From 1b2ea61345fe8e515e2f0fde903ca5dfdf117f86 Mon Sep 17 00:00:00 2001 From: doctorpangloss <@hiddenswitch.com> Date: Wed, 7 Feb 2024 14:20:21 -0800 Subject: [PATCH] Improved API support - Run comfyui workflows directly inside other python applications using EmbeddedComfyClient. - Optional telemetry in prompts and models using anonymity preserving Plausible self-hosted or hosted. - Better OpenAPI schema - Basic support for distributed ComfyUI backends. Limitations: no progress reporting, no easy way to start your own distributed backend, requires RabbitMQ as a message broker. --- comfy/analytics/__init__.py | 0 comfy/analytics/analytics.py | 96 ++ comfy/analytics/event_tracker.py | 37 + comfy/analytics/identity_provider_nt.py | 34 + comfy/analytics/multi_event_tracker.py | 37 + comfy/analytics/plausible.py | 84 + comfy/api/__init__.py | 22 +- comfy/api/api_client.py | 1402 +++++++++++++++- comfy/api/api_response.py | 28 + comfy/api/apis/__init__.py | 3 + comfy/api/apis/path_to_api.py | 50 + comfy/api/apis/paths/__init__.py | 3 + comfy/api/apis/paths/api_v1_images_digest.py | 13 + comfy/api/apis/paths/api_v1_prompts.py | 15 + comfy/api/apis/paths/embeddings.py | 13 + comfy/api/apis/paths/extensions.py | 13 + comfy/api/apis/paths/history.py | 15 + comfy/api/apis/paths/interrupt.py | 13 + comfy/api/apis/paths/object_info.py | 13 + comfy/api/apis/paths/prompt.py | 15 + comfy/api/apis/paths/queue.py | 15 + comfy/api/apis/paths/solidus.py | 13 + comfy/api/apis/paths/upload_image.py | 13 + comfy/api/apis/paths/view.py | 13 + comfy/api/apis/tag_to_api.py | 17 + comfy/api/apis/tags/__init__.py | 3 + comfy/api/apis/tags/default_api.py | 48 + comfy/api/components/__init__.py | 0 comfy/api/components/schema/__init__.py | 5 + comfy/api/components/schema/extra_data.py | 205 +++ comfy/api/components/schema/node.py | 959 +++++++++++ comfy/api/components/schema/prompt.py | 100 ++ comfy/api/components/schema/prompt_node.py | 405 +++++ comfy/api/components/schema/prompt_request.py | 149 ++ comfy/api/components/schema/queue_tuple.py | 166 ++ comfy/api/components/schema/workflow.py | 1457 +++++++++++++++++ comfy/api/components/schemas/__init__.py | 20 + comfy/api/configuration.py | 2 - comfy/api/configurations/__init__.py | 0 comfy/api/configurations/api_configuration.py | 281 ++++ .../configurations/schema_configuration.py | 108 ++ comfy/api/exceptions.py | 134 +- comfy/api/openapi.yaml | 177 +- comfy/api/openapi_python_config.yaml | 2 +- comfy/api/paths/__init__.py | 3 + .../paths/api_v1_images_digest/__init__.py | 5 + .../api_v1_images_digest/get/__init__.py | 0 .../api_v1_images_digest/get/operation.py | 162 ++ .../get/parameters/__init__.py | 0 .../get/parameters/parameter_0/__init__.py | 16 + .../get/parameters/parameter_0/schema.py | 13 + .../get/path_parameters.py | 97 ++ .../get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/image_png/__init__.py | 0 .../response_200/content/image_png/schema.py | 13 + .../get/responses/response_404/__init__.py | 19 + comfy/api/paths/api_v1_prompts/__init__.py | 5 + .../api/paths/api_v1_prompts/get/__init__.py | 0 .../api/paths/api_v1_prompts/get/operation.py | 136 ++ .../api_v1_prompts/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 13 + .../get/responses/response_404/__init__.py | 19 + .../api/paths/api_v1_prompts/post/__init__.py | 0 .../paths/api_v1_prompts/post/operation.py | 240 +++ .../post/request_body/__init__.py | 25 + .../post/request_body/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 13 + .../content/multipart_formdata/__init__.py | 0 .../content/multipart_formdata/schema.py | 186 +++ .../api_v1_prompts/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 39 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 156 ++ .../response_200/header_parameters.py | 131 ++ .../response_200/headers/__init__.py | 0 .../header_content_disposition/__init__.py | 14 + .../header_content_disposition/schema.py | 13 + .../headers/header_digest/__init__.py | 14 + .../headers/header_digest/schema.py | 13 + .../headers/header_location/__init__.py | 14 + .../headers/header_location/schema.py | 13 + .../post/responses/response_204/__init__.py | 19 + .../post/responses/response_400/__init__.py | 19 + .../post/responses/response_429/__init__.py | 19 + .../post/responses/response_500/__init__.py | 19 + .../post/responses/response_503/__init__.py | 19 + .../post/responses/response_507/__init__.py | 19 + comfy/api/paths/embeddings/__init__.py | 5 + comfy/api/paths/embeddings/get/__init__.py | 0 comfy/api/paths/embeddings/get/operation.py | 114 ++ .../embeddings/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 63 + comfy/api/paths/extensions/__init__.py | 5 + comfy/api/paths/extensions/get/__init__.py | 0 comfy/api/paths/extensions/get/operation.py | 114 ++ .../extensions/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 63 + comfy/api/paths/history/__init__.py | 5 + comfy/api/paths/history/get/__init__.py | 0 comfy/api/paths/history/get/operation.py | 114 ++ .../paths/history/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 221 +++ comfy/api/paths/history/post/__init__.py | 0 comfy/api/paths/history/post/operation.py | 136 ++ .../history/post/request_body/__init__.py | 19 + .../post/request_body/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 174 ++ .../paths/history/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 19 + comfy/api/paths/interrupt/__init__.py | 5 + comfy/api/paths/interrupt/post/__init__.py | 0 comfy/api/paths/interrupt/post/operation.py | 105 ++ .../interrupt/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 19 + comfy/api/paths/object_info/__init__.py | 5 + comfy/api/paths/object_info/get/__init__.py | 0 comfy/api/paths/object_info/get/operation.py | 114 ++ .../object_info/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 90 + comfy/api/paths/prompt/__init__.py | 5 + comfy/api/paths/prompt/get/__init__.py | 0 comfy/api/paths/prompt/get/operation.py | 114 ++ .../paths/prompt/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 198 +++ comfy/api/paths/prompt/post/__init__.py | 0 comfy/api/paths/prompt/post/operation.py | 165 ++ .../prompt/post/request_body/__init__.py | 19 + .../post/request_body/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 13 + .../paths/prompt/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/text_plain/__init__.py | 0 .../response_200/content/text_plain/schema.py | 13 + .../post/responses/response_400/__init__.py | 28 + .../response_400/content/__init__.py | 0 .../content/text_plain/__init__.py | 0 .../response_400/content/text_plain/schema.py | 13 + comfy/api/paths/queue/__init__.py | 5 + comfy/api/paths/queue/get/__init__.py | 0 comfy/api/paths/queue/get/operation.py | 114 ++ .../api/paths/queue/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 237 +++ comfy/api/paths/queue/post/__init__.py | 0 comfy/api/paths/queue/post/operation.py | 136 ++ .../paths/queue/post/request_body/__init__.py | 19 + .../post/request_body/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 174 ++ .../paths/queue/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 19 + comfy/api/paths/solidus/__init__.py | 5 + comfy/api/paths/solidus/get/__init__.py | 0 comfy/api/paths/solidus/get/operation.py | 114 ++ .../paths/solidus/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 27 + comfy/api/paths/upload_image/__init__.py | 5 + comfy/api/paths/upload_image/post/__init__.py | 0 .../api/paths/upload_image/post/operation.py | 165 ++ .../post/request_body/__init__.py | 19 + .../post/request_body/content/__init__.py | 0 .../content/multipart_form_data/__init__.py | 0 .../content/multipart_form_data/schema.py | 108 ++ .../upload_image/post/responses/__init__.py | 0 .../post/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/application_json/__init__.py | 0 .../content/application_json/schema.py | 105 ++ .../post/responses/response_400/__init__.py | 19 + comfy/api/paths/view/__init__.py | 5 + comfy/api/paths/view/get/__init__.py | 0 comfy/api/paths/view/get/operation.py | 179 ++ .../api/paths/view/get/parameters/__init__.py | 0 .../get/parameters/parameter_0/__init__.py | 17 + .../view/get/parameters/parameter_0/schema.py | 13 + .../get/parameters/parameter_1/__init__.py | 16 + .../view/get/parameters/parameter_1/schema.py | 94 ++ .../get/parameters/parameter_2/__init__.py | 16 + .../view/get/parameters/parameter_2/schema.py | 13 + comfy/api/paths/view/get/query_parameters.py | 161 ++ .../api/paths/view/get/responses/__init__.py | 0 .../get/responses/response_200/__init__.py | 28 + .../response_200/content/__init__.py | 0 .../content/image_png/__init__.py | 0 .../response_200/content/image_png/schema.py | 13 + .../get/responses/response_400/__init__.py | 19 + .../get/responses/response_403/__init__.py | 19 + .../get/responses/response_404/__init__.py | 19 + comfy/api/py.typed | 0 comfy/api/rest.py | 270 +++ comfy/api/schemas/__init__.py | 148 ++ comfy/api/schemas/format.py | 115 ++ comfy/api/schemas/original_immutabledict.py | 97 ++ comfy/api/schemas/schema.py | 729 +++++++++ comfy/api/schemas/schemas.py | 375 +++++ comfy/api/schemas/validation.py | 1446 ++++++++++++++++ comfy/api/security_schemes.py | 227 +++ comfy/api/server.py | 34 + comfy/api/servers/__init__.py | 0 comfy/api/servers/server_0.py | 14 + comfy/api/shared_imports/__init__.py | 0 comfy/api/shared_imports/header_imports.py | 15 + comfy/api/shared_imports/operation_imports.py | 18 + comfy/api/shared_imports/response_imports.py | 25 + comfy/api/shared_imports/schema_imports.py | 28 + .../shared_imports/security_scheme_imports.py | 12 + comfy/api/shared_imports/server_imports.py | 13 + comfy/cli_args.py | 27 +- comfy/cli_args_types.py | 137 ++ comfy/client/__init__.py | 0 comfy/client/aio_client.py | 96 ++ comfy/client/embedded_comfy_client.py | 110 ++ comfy/client/sdxl_with_refiner_workflow.py | 188 +++ comfy/cmd/execution.py | 163 +- comfy/cmd/latent_preview.py | 3 +- comfy/cmd/main.py | 13 +- comfy/cmd/openapi_gen.py | 12 +- comfy/cmd/server.py | 126 +- comfy/component_model/__init__.py | 0 .../component_model/abstract_prompt_queue.py | 112 ++ comfy/component_model/executor_types.py | 56 + comfy/component_model/file_output_path.py | 40 + comfy/component_model/make_mutable.py | 20 + comfy/component_model/queue_types.py | 93 ++ comfy/distributed/__init__.py | 0 comfy/distributed/distributed_prompt_queue.py | 198 +++ .../distributed/distributed_prompt_worker.py | 50 + comfy/distributed/history.py | 29 + comfy/nodes/package_typing.py | 5 + comfy/sampler_names.py | 5 + comfy/samplers.py | 8 +- comfy_extras/nodes/nodes_custom_sampler.py | 5 +- comfy_extras/nodes/nodes_stable3d.py | 4 +- requirements-dev.txt | 5 +- requirements.txt | 4 +- tests/compare/conftest.py | 2 +- tests/compare/test_quality.py | 9 +- tests/conftest.py | 59 +- tests/distributed/__init__.py | 0 .../distributed/test_asyncio_remote_client.py | 19 + tests/distributed/test_distributed_queue.py | 39 + tests/distributed/test_embedded_client.py | 33 + .../graphs/default_graph_sdxl1_0.json | 266 +-- tests/inference/test_inference.py | 31 +- 271 files changed, 16925 insertions(+), 413 deletions(-) create mode 100644 comfy/analytics/__init__.py create mode 100644 comfy/analytics/analytics.py create mode 100644 comfy/analytics/event_tracker.py create mode 100644 comfy/analytics/identity_provider_nt.py create mode 100644 comfy/analytics/multi_event_tracker.py create mode 100644 comfy/analytics/plausible.py create mode 100644 comfy/api/api_response.py create mode 100644 comfy/api/apis/__init__.py create mode 100644 comfy/api/apis/path_to_api.py create mode 100644 comfy/api/apis/paths/__init__.py create mode 100644 comfy/api/apis/paths/api_v1_images_digest.py create mode 100644 comfy/api/apis/paths/api_v1_prompts.py create mode 100644 comfy/api/apis/paths/embeddings.py create mode 100644 comfy/api/apis/paths/extensions.py create mode 100644 comfy/api/apis/paths/history.py create mode 100644 comfy/api/apis/paths/interrupt.py create mode 100644 comfy/api/apis/paths/object_info.py create mode 100644 comfy/api/apis/paths/prompt.py create mode 100644 comfy/api/apis/paths/queue.py create mode 100644 comfy/api/apis/paths/solidus.py create mode 100644 comfy/api/apis/paths/upload_image.py create mode 100644 comfy/api/apis/paths/view.py create mode 100644 comfy/api/apis/tag_to_api.py create mode 100644 comfy/api/apis/tags/__init__.py create mode 100644 comfy/api/apis/tags/default_api.py create mode 100644 comfy/api/components/__init__.py create mode 100644 comfy/api/components/schema/__init__.py create mode 100644 comfy/api/components/schema/extra_data.py create mode 100644 comfy/api/components/schema/node.py create mode 100644 comfy/api/components/schema/prompt.py create mode 100644 comfy/api/components/schema/prompt_node.py create mode 100644 comfy/api/components/schema/prompt_request.py create mode 100644 comfy/api/components/schema/queue_tuple.py create mode 100644 comfy/api/components/schema/workflow.py create mode 100644 comfy/api/components/schemas/__init__.py delete mode 100644 comfy/api/configuration.py create mode 100644 comfy/api/configurations/__init__.py create mode 100644 comfy/api/configurations/api_configuration.py create mode 100644 comfy/api/configurations/schema_configuration.py create mode 100644 comfy/api/paths/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/operation.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/parameters/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/schema.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/path_parameters.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/__init__.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/schema.py create mode 100644 comfy/api/paths/api_v1_images_digest/get/responses/response_404/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/operation.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/get/responses/response_404/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/operation.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/content/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/header_parameters.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/schema.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_204/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_400/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_429/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_500/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_503/__init__.py create mode 100644 comfy/api/paths/api_v1_prompts/post/responses/response_507/__init__.py create mode 100644 comfy/api/paths/embeddings/__init__.py create mode 100644 comfy/api/paths/embeddings/get/__init__.py create mode 100644 comfy/api/paths/embeddings/get/operation.py create mode 100644 comfy/api/paths/embeddings/get/responses/__init__.py create mode 100644 comfy/api/paths/embeddings/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/embeddings/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/embeddings/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/embeddings/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/extensions/__init__.py create mode 100644 comfy/api/paths/extensions/get/__init__.py create mode 100644 comfy/api/paths/extensions/get/operation.py create mode 100644 comfy/api/paths/extensions/get/responses/__init__.py create mode 100644 comfy/api/paths/extensions/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/extensions/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/extensions/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/extensions/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/history/__init__.py create mode 100644 comfy/api/paths/history/get/__init__.py create mode 100644 comfy/api/paths/history/get/operation.py create mode 100644 comfy/api/paths/history/get/responses/__init__.py create mode 100644 comfy/api/paths/history/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/history/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/history/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/history/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/history/post/__init__.py create mode 100644 comfy/api/paths/history/post/operation.py create mode 100644 comfy/api/paths/history/post/request_body/__init__.py create mode 100644 comfy/api/paths/history/post/request_body/content/__init__.py create mode 100644 comfy/api/paths/history/post/request_body/content/application_json/__init__.py create mode 100644 comfy/api/paths/history/post/request_body/content/application_json/schema.py create mode 100644 comfy/api/paths/history/post/responses/__init__.py create mode 100644 comfy/api/paths/history/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/interrupt/__init__.py create mode 100644 comfy/api/paths/interrupt/post/__init__.py create mode 100644 comfy/api/paths/interrupt/post/operation.py create mode 100644 comfy/api/paths/interrupt/post/responses/__init__.py create mode 100644 comfy/api/paths/interrupt/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/object_info/__init__.py create mode 100644 comfy/api/paths/object_info/get/__init__.py create mode 100644 comfy/api/paths/object_info/get/operation.py create mode 100644 comfy/api/paths/object_info/get/responses/__init__.py create mode 100644 comfy/api/paths/object_info/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/object_info/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/object_info/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/object_info/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/prompt/__init__.py create mode 100644 comfy/api/paths/prompt/get/__init__.py create mode 100644 comfy/api/paths/prompt/get/operation.py create mode 100644 comfy/api/paths/prompt/get/responses/__init__.py create mode 100644 comfy/api/paths/prompt/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/prompt/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/prompt/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/prompt/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/prompt/post/__init__.py create mode 100644 comfy/api/paths/prompt/post/operation.py create mode 100644 comfy/api/paths/prompt/post/request_body/__init__.py create mode 100644 comfy/api/paths/prompt/post/request_body/content/__init__.py create mode 100644 comfy/api/paths/prompt/post/request_body/content/application_json/__init__.py create mode 100644 comfy/api/paths/prompt/post/request_body/content/application_json/schema.py create mode 100644 comfy/api/paths/prompt/post/responses/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_200/content/text_plain/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_200/content/text_plain/schema.py create mode 100644 comfy/api/paths/prompt/post/responses/response_400/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_400/content/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_400/content/text_plain/__init__.py create mode 100644 comfy/api/paths/prompt/post/responses/response_400/content/text_plain/schema.py create mode 100644 comfy/api/paths/queue/__init__.py create mode 100644 comfy/api/paths/queue/get/__init__.py create mode 100644 comfy/api/paths/queue/get/operation.py create mode 100644 comfy/api/paths/queue/get/responses/__init__.py create mode 100644 comfy/api/paths/queue/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/queue/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/queue/get/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/queue/get/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/queue/post/__init__.py create mode 100644 comfy/api/paths/queue/post/operation.py create mode 100644 comfy/api/paths/queue/post/request_body/__init__.py create mode 100644 comfy/api/paths/queue/post/request_body/content/__init__.py create mode 100644 comfy/api/paths/queue/post/request_body/content/application_json/__init__.py create mode 100644 comfy/api/paths/queue/post/request_body/content/application_json/schema.py create mode 100644 comfy/api/paths/queue/post/responses/__init__.py create mode 100644 comfy/api/paths/queue/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/solidus/__init__.py create mode 100644 comfy/api/paths/solidus/get/__init__.py create mode 100644 comfy/api/paths/solidus/get/operation.py create mode 100644 comfy/api/paths/solidus/get/responses/__init__.py create mode 100644 comfy/api/paths/solidus/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/upload_image/__init__.py create mode 100644 comfy/api/paths/upload_image/post/__init__.py create mode 100644 comfy/api/paths/upload_image/post/operation.py create mode 100644 comfy/api/paths/upload_image/post/request_body/__init__.py create mode 100644 comfy/api/paths/upload_image/post/request_body/content/__init__.py create mode 100644 comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/__init__.py create mode 100644 comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/schema.py create mode 100644 comfy/api/paths/upload_image/post/responses/__init__.py create mode 100644 comfy/api/paths/upload_image/post/responses/response_200/__init__.py create mode 100644 comfy/api/paths/upload_image/post/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/upload_image/post/responses/response_200/content/application_json/__init__.py create mode 100644 comfy/api/paths/upload_image/post/responses/response_200/content/application_json/schema.py create mode 100644 comfy/api/paths/upload_image/post/responses/response_400/__init__.py create mode 100644 comfy/api/paths/view/__init__.py create mode 100644 comfy/api/paths/view/get/__init__.py create mode 100644 comfy/api/paths/view/get/operation.py create mode 100644 comfy/api/paths/view/get/parameters/__init__.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_0/__init__.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_0/schema.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_1/__init__.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_1/schema.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_2/__init__.py create mode 100644 comfy/api/paths/view/get/parameters/parameter_2/schema.py create mode 100644 comfy/api/paths/view/get/query_parameters.py create mode 100644 comfy/api/paths/view/get/responses/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_200/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_200/content/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_200/content/image_png/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_200/content/image_png/schema.py create mode 100644 comfy/api/paths/view/get/responses/response_400/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_403/__init__.py create mode 100644 comfy/api/paths/view/get/responses/response_404/__init__.py create mode 100644 comfy/api/py.typed create mode 100644 comfy/api/rest.py create mode 100644 comfy/api/schemas/__init__.py create mode 100644 comfy/api/schemas/format.py create mode 100644 comfy/api/schemas/original_immutabledict.py create mode 100644 comfy/api/schemas/schema.py create mode 100644 comfy/api/schemas/schemas.py create mode 100644 comfy/api/schemas/validation.py create mode 100644 comfy/api/security_schemes.py create mode 100644 comfy/api/server.py create mode 100644 comfy/api/servers/__init__.py create mode 100644 comfy/api/servers/server_0.py create mode 100644 comfy/api/shared_imports/__init__.py create mode 100644 comfy/api/shared_imports/header_imports.py create mode 100644 comfy/api/shared_imports/operation_imports.py create mode 100644 comfy/api/shared_imports/response_imports.py create mode 100644 comfy/api/shared_imports/schema_imports.py create mode 100644 comfy/api/shared_imports/security_scheme_imports.py create mode 100644 comfy/api/shared_imports/server_imports.py create mode 100644 comfy/cli_args_types.py create mode 100644 comfy/client/__init__.py create mode 100644 comfy/client/aio_client.py create mode 100644 comfy/client/embedded_comfy_client.py create mode 100644 comfy/client/sdxl_with_refiner_workflow.py create mode 100644 comfy/component_model/__init__.py create mode 100644 comfy/component_model/abstract_prompt_queue.py create mode 100644 comfy/component_model/executor_types.py create mode 100644 comfy/component_model/file_output_path.py create mode 100644 comfy/component_model/make_mutable.py create mode 100644 comfy/component_model/queue_types.py create mode 100644 comfy/distributed/__init__.py create mode 100644 comfy/distributed/distributed_prompt_queue.py create mode 100644 comfy/distributed/distributed_prompt_worker.py create mode 100644 comfy/distributed/history.py create mode 100644 comfy/sampler_names.py create mode 100644 tests/distributed/__init__.py create mode 100644 tests/distributed/test_asyncio_remote_client.py create mode 100644 tests/distributed/test_distributed_queue.py create mode 100644 tests/distributed/test_embedded_client.py diff --git a/comfy/analytics/__init__.py b/comfy/analytics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/analytics/analytics.py b/comfy/analytics/analytics.py new file mode 100644 index 000000000..d0b8bfd15 --- /dev/null +++ b/comfy/analytics/analytics.py @@ -0,0 +1,96 @@ +import asyncio +import json +import sys +import uuid +from typing import Optional + +from .multi_event_tracker import MultiEventTracker +from .plausible import PlausibleTracker +from ..api.components.schema.prompt import Prompt + +_event_tracker: MultiEventTracker + + +def initialize_event_tracking(loop: asyncio.AbstractEventLoop): + _event_trackers = [] + # perform the imports at the time this is invoked to prevent side effects and ordering issues + from ..cli_args import args + + identity = str(uuid.uuid4()) + if args.analytics_use_identity_provider and sys.platform == "nt": + from .identity_provider_nt import get_user_name + identity = get_user_name() + + if args.plausible_analytics_domain is not None and args.plausible_analytics_base_url is not None: + _event_trackers.append(PlausibleTracker(loop, user_agent=identity, base_url=args.plausible_analytics_base_url, + domain=args.plausible_analytics_domain)) + + if len(_event_trackers) == 0: + return + + _event_tracker = MultiEventTracker(_event_trackers) + + def track_event(name: str, url: str = "app://comfyui", props: Optional[dict] = None): + # not awaited, we don't care about event tracking in terms of blocking + loop.create_task(_event_tracker.track_event(name, url, props=props)) + + # patch nodes + from ..nodes.base_nodes import SaveImage, CLIPTextEncode, LoraLoader, CheckpointLoaderSimple + from ..cmd.execution import PromptQueue + from comfy.component_model.queue_types import QueueItem + + prompt_queue_put = PromptQueue.put + + def prompt_queue_put_tracked(self: PromptQueue, item: QueueItem): + prompt = Prompt.validate(item.prompt) + + samplers = [v for _, v in prompt.items() if + "positive" in v.inputs and "negative" in v.inputs] + + positive_prompt_ids = [] + negative_prompt_ids = [] + for sampler in samplers: + try: + # duck typed + key, _ = sampler.inputs['positive'] + positive_prompt_ids.append(key) + except: + pass + try: + key, _ = sampler.inputs['negative'] + negative_prompt_ids.append(key) + except: + pass + + positive_prompts = "; ".join(frozenset(str(prompt[x].inputs["text"]) for x in positive_prompt_ids if + prompt[x].class_type == CLIPTextEncode.__name__)) + negative_prompts = "; ".join(frozenset(str(prompt[x].inputs["text"]) for x in negative_prompt_ids if + prompt[x].class_type == CLIPTextEncode.__name__)) + loras = "; ".join(frozenset( + str(node.inputs["lora_name"]) for node in prompt.values() if + node.class_type == LoraLoader.__name__)) + checkpoints = "; ".join(frozenset(str(node.inputs["ckpt_name"]) for node in prompt.values() if + node.class_type == CheckpointLoaderSimple.__name__)) + prompt_str = json.dumps(item.queue_tuple, separators=(',', ':')) + len_prompt_str = len(prompt_str) + prompt_str_pieces = [] + for i in range(0, len_prompt_str, 1000): + prompt_str_pieces += [prompt_str[i:min(i + 1000, len_prompt_str)]] + prompt_str_props = {} + for i, prompt_str_piece in enumerate(prompt_str_pieces): + prompt_str_props[f"prompt.{i}"] = prompt_str_piece + try: + track_event(SaveImage.__name__, props={ + "positive_prompts": positive_prompts, + "negative_prompts": negative_prompts, + "loras": loras, + "checkpoints": checkpoints, + **prompt_str_props + }) + except: + # prevent analytics exceptions from cursing us + pass + + return prompt_queue_put(self, item) + + PromptQueue.put = prompt_queue_put_tracked diff --git a/comfy/analytics/event_tracker.py b/comfy/analytics/event_tracker.py new file mode 100644 index 000000000..abdc81572 --- /dev/null +++ b/comfy/analytics/event_tracker.py @@ -0,0 +1,37 @@ +from abc import ABC, abstractmethod +import asyncio +from typing import Optional, Dict, Any, Union + + +class EventTracker(ABC): + def __init__(self) -> None: + pass + + @property + @abstractmethod + def user_agent(self) -> str: + pass + + @user_agent.setter + @abstractmethod + def user_agent(self, value: str) -> None: + pass + + @property + @abstractmethod + def domain(self) -> str: + pass + + @domain.setter + @abstractmethod + def domain(self, value: str) -> None: + pass + + @abstractmethod + async def track_event(self, name: str, url: str, referrer: Optional[str] = None, + props: Optional[Dict[str, Any]] = None) -> str: + pass + + @abstractmethod + async def close(self) -> None: + pass diff --git a/comfy/analytics/identity_provider_nt.py b/comfy/analytics/identity_provider_nt.py new file mode 100644 index 000000000..149184398 --- /dev/null +++ b/comfy/analytics/identity_provider_nt.py @@ -0,0 +1,34 @@ +import ctypes +from ctypes import wintypes, POINTER, byref + +_windows_dll = ctypes.WinDLL('Secur32.dll') + +_windows_get_user_name_ex_w_func = _windows_dll.GetUserNameExW +_windows_get_user_name_ex_w_func.argtypes = [ctypes.c_int, POINTER(wintypes.WCHAR), POINTER(wintypes.ULONG)] +_windows_get_user_name_ex_w_func.restype = wintypes.BOOL + +_windows_extended_name_format = { + "NameUnknown": 0, + "NameFullyQualifiedDN": 1, + "NameSamCompatible": 2, + "NameDisplay": 3, + "NameUniqueId": 6, + "NameCanonical": 7, + "NameUserPrincipal": 8, + "NameCanonicalEx": 9, + "NameServicePrincipal": 10, + "NameDnsDomain": 12 +} + + +def get_user_name(): + size = wintypes.ULONG(0) + format_type = _windows_extended_name_format["NameDisplay"] + _windows_get_user_name_ex_w_func(format_type, None, byref(size)) + + name_buffer = ctypes.create_unicode_buffer(size.value) + + if not _windows_get_user_name_ex_w_func(format_type, name_buffer, byref(size)): + return None + + return name_buffer.value diff --git a/comfy/analytics/multi_event_tracker.py b/comfy/analytics/multi_event_tracker.py new file mode 100644 index 000000000..4c1f670cd --- /dev/null +++ b/comfy/analytics/multi_event_tracker.py @@ -0,0 +1,37 @@ +import asyncio +from typing import List, Optional, Dict, Any, Union + +from .event_tracker import EventTracker + + +class MultiEventTracker(EventTracker): + def __init__(self, trackers: List[EventTracker]) -> None: + super().__init__() + self.trackers = trackers + + async def track_event(self, name: str, url: str, referrer: Optional[str] = None, + props: Optional[Dict[str, Any]] = None) -> None: + tasks = [tracker.track_event(name, url, referrer, props) for tracker in self.trackers] + await asyncio.gather(*tasks) + + async def close(self) -> None: + tasks = [tracker.close() for tracker in self.trackers] + await asyncio.gather(*tasks) + + @property + def user_agent(self) -> str: + return next(tracker.user_agent for tracker in self.trackers) if len(self.trackers) > 0 else "(unknown)" + + @user_agent.setter + def user_agent(self, value: str) -> None: + for tracker in self.trackers: + tracker.user_agent = value + + @property + def domain(self) -> str: + return next(tracker.domain for tracker in self.trackers) if len(self.trackers) > 0 else ("unknown") + + @domain.setter + def domain(self, value: str) -> None: + for tracker in self.trackers: + tracker.domain = value diff --git a/comfy/analytics/plausible.py b/comfy/analytics/plausible.py new file mode 100644 index 000000000..e2263ce9c --- /dev/null +++ b/comfy/analytics/plausible.py @@ -0,0 +1,84 @@ +import asyncio +import json +import typing +from typing import Optional, Dict, Any + +import aiohttp + +from .event_tracker import EventTracker + + +class PlausibleTracker(EventTracker): + def __init__(self, loop: asyncio.AbstractEventLoop, user_agent: str, base_url: str, domain: str) -> None: + super().__init__() + self._user_agent = user_agent + self._domain = domain + self._base_url = base_url + self.loop = loop + self.session = aiohttp.ClientSession(loop=self.loop) + self._public_ip: typing.Literal[False] | None | str = None + + @property + def user_agent(self) -> str: + return self._user_agent + + @user_agent.setter + def user_agent(self, value: str) -> None: + self._user_agent = value + + @property + def domain(self) -> str: + return self._domain + + @domain.setter + def domain(self, value: str) -> None: + self._domain = value + + @property + def base_url(self) -> str: + return self._base_url + + @base_url.setter + def base_url(self, value: str) -> None: + self._base_url = value + + async def get_public_ip(self): + try: + async with self.session.get('https://www.cloudflare.com/cdn-cgi/trace') as response: + if response.status == 200: + text = await response.text() + for line in text.splitlines(): + if line.startswith('ip='): + ip_address = line.split('=')[1] + return ip_address + except: + return False + + async def track_event(self, name: str, url: str, referrer: Optional[str] = None, + props: Optional[Dict[str, Any]] = None) -> str: + + if self._public_ip is None: + self._public_ip = await self.get_public_ip() + headers = { + 'User-Agent': self.user_agent, + 'Content-Type': 'application/json', + } + if self._public_ip is not None and self._public_ip != False: + headers['X-Forwarded-For'] = self._public_ip + + data = { + 'name': name, + 'url': url, + 'domain': self.domain + } + if referrer: + data['referrer'] = referrer + if props: + data['props'] = props + + async with self.session.post(f'{self.base_url}/api/event', headers=headers, + data=json.dumps(data)) as response: + return await response.text() + + async def close(self) -> None: + await self.session.close() diff --git a/comfy/api/__init__.py b/comfy/api/__init__.py index b647128f5..2b7aae6a8 100644 --- a/comfy/api/__init__.py +++ b/comfy/api/__init__.py @@ -4,25 +4,23 @@ """ comfyui - - No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 - + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 The version of the OpenAPI document: 0.0.1 - Generated by: https://openapi-generator.tech + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator """ __version__ = "1.0.0" # import ApiClient -from .api_client import ApiClient +from comfy.api.api_client import ApiClient # import Configuration -from .configuration import Configuration +from comfy.api.configurations.api_configuration import ApiConfiguration # import exceptions -from .exceptions import OpenApiException -from .exceptions import ApiAttributeError -from .exceptions import ApiTypeError -from .exceptions import ApiValueError -from .exceptions import ApiKeyError -from .exceptions import ApiException +from comfy.api.exceptions import OpenApiException +from comfy.api.exceptions import ApiAttributeError +from comfy.api.exceptions import ApiTypeError +from comfy.api.exceptions import ApiValueError +from comfy.api.exceptions import ApiKeyError +from comfy.api.exceptions import ApiException diff --git a/comfy/api/api_client.py b/comfy/api/api_client.py index 12636c7db..dd9efbc59 100644 --- a/comfy/api/api_client.py +++ b/comfy/api/api_client.py @@ -1,2 +1,1402 @@ +# coding: utf-8 +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +import abc +import datetime +import dataclasses +import decimal +import enum +import email +import json +import os +import io +import atexit +from multiprocessing import pool +import re +import tempfile +import typing +import typing_extensions +from urllib import parse +import urllib3 +from urllib3 import _collections, fields + + +from comfy.api import exceptions, rest, schemas, security_schemes, api_response +from comfy.api.configurations import api_configuration, schema_configuration as schema_configuration_ + + +class JSONEncoder(json.JSONEncoder): + compact_separators = (',', ':') + + def default(self, obj: typing.Any): + if isinstance(obj, str): + return str(obj) + elif isinstance(obj, float): + return obj + elif isinstance(obj, bool): + # must be before int check + return obj + elif isinstance(obj, int): + return obj + elif obj is None: + return None + elif isinstance(obj, (dict, schemas.immutabledict)): + return {key: self.default(val) for key, val in obj.items()} + elif isinstance(obj, (list, tuple)): + return [self.default(item) for item in obj] + raise exceptions.ApiValueError('Unable to prepare type {} for serialization'.format(obj.__class__.__name__)) + + +class ParameterInType(enum.Enum): + QUERY = 'query' + HEADER = 'header' + PATH = 'path' + COOKIE = 'cookie' + + +class ParameterStyle(enum.Enum): + MATRIX = 'matrix' + LABEL = 'label' + FORM = 'form' + SIMPLE = 'simple' + SPACE_DELIMITED = 'spaceDelimited' + PIPE_DELIMITED = 'pipeDelimited' + DEEP_OBJECT = 'deepObject' + + +@dataclasses.dataclass +class PrefixSeparatorIterator: + # A class to store prefixes and separators for rfc6570 expansions + prefix: str + separator: str + first: bool = True + item_separator: str = dataclasses.field(init=False) + + def __post_init__(self): + self.item_separator = self.separator if self.separator in {'.', '|', '%20'} else ',' + + def __iter__(self): + return self + + def __next__(self): + if self.first: + self.first = False + return self.prefix + return self.separator + + +class ParameterSerializerBase: + @staticmethod + def __ref6570_item_value(in_data: typing.Any, percent_encode: bool): + """ + Get representation if str/float/int/None/items in list/ values in dict + None is returned if an item is undefined, use cases are value= + - None + - [] + - {} + - [None, None None] + - {'a': None, 'b': None} + """ + if type(in_data) in {str, float, int}: + if percent_encode: + return parse.quote(str(in_data)) + return str(in_data) + elif in_data is None: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return None + elif isinstance(in_data, list) and not in_data: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return None + elif isinstance(in_data, dict) and not in_data: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return None + raise exceptions.ApiValueError('Unable to generate a ref6570 item representation of {}'.format(in_data)) + + @staticmethod + def _to_dict(name: str, value: str): + return {name: value} + + @classmethod + def __ref6570_str_float_int_expansion( + cls, + variable_name: str, + in_data: typing.Any, + explode: bool, + percent_encode: bool, + prefix_separator_iterator: PrefixSeparatorIterator, + var_name_piece: str, + named_parameter_expansion: bool + ) -> str: + item_value = cls.__ref6570_item_value(in_data, percent_encode) + if item_value is None or (item_value == '' and prefix_separator_iterator.separator == ';'): + return next(prefix_separator_iterator) + var_name_piece + value_pair_equals = '=' if named_parameter_expansion else '' + return next(prefix_separator_iterator) + var_name_piece + value_pair_equals + item_value + + @classmethod + def __ref6570_list_expansion( + cls, + variable_name: str, + in_data: typing.Any, + explode: bool, + percent_encode: bool, + prefix_separator_iterator: PrefixSeparatorIterator, + var_name_piece: str, + named_parameter_expansion: bool + ) -> str: + item_values = [cls.__ref6570_item_value(v, percent_encode) for v in in_data] + item_values = [v for v in item_values if v is not None] + if not item_values: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return "" + value_pair_equals = '=' if named_parameter_expansion else '' + if not explode: + return ( + next(prefix_separator_iterator) + + var_name_piece + + value_pair_equals + + prefix_separator_iterator.item_separator.join(item_values) + ) + # exploded + return next(prefix_separator_iterator) + next(prefix_separator_iterator).join( + [var_name_piece + value_pair_equals + val for val in item_values] + ) + + @classmethod + def __ref6570_dict_expansion( + cls, + variable_name: str, + in_data: typing.Any, + explode: bool, + percent_encode: bool, + prefix_separator_iterator: PrefixSeparatorIterator, + var_name_piece: str, + named_parameter_expansion: bool + ) -> str: + in_data_transformed = {key: cls.__ref6570_item_value(val, percent_encode) for key, val in in_data.items()} + in_data_transformed = {key: val for key, val in in_data_transformed.items() if val is not None} + if not in_data_transformed: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return "" + value_pair_equals = '=' if named_parameter_expansion else '' + if not explode: + return ( + next(prefix_separator_iterator) + + var_name_piece + value_pair_equals + + prefix_separator_iterator.item_separator.join( + prefix_separator_iterator.item_separator.join( + item_pair + ) for item_pair in in_data_transformed.items() + ) + ) + # exploded + return next(prefix_separator_iterator) + next(prefix_separator_iterator).join( + [key + '=' + val for key, val in in_data_transformed.items()] + ) + + @classmethod + def _ref6570_expansion( + cls, + variable_name: str, + in_data: typing.Any, + explode: bool, + percent_encode: bool, + prefix_separator_iterator: PrefixSeparatorIterator + ) -> str: + """ + Separator is for separate variables like dict with explode true, not for array item separation + """ + named_parameter_expansion = prefix_separator_iterator.separator in {'&', ';'} + var_name_piece = variable_name if named_parameter_expansion else '' + if type(in_data) in {str, float, int}: + return cls.__ref6570_str_float_int_expansion( + variable_name, + in_data, + explode, + percent_encode, + prefix_separator_iterator, + var_name_piece, + named_parameter_expansion + ) + elif in_data is None: + # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1 + return "" + elif isinstance(in_data, list): + return cls.__ref6570_list_expansion( + variable_name, + in_data, + explode, + percent_encode, + prefix_separator_iterator, + var_name_piece, + named_parameter_expansion + ) + elif isinstance(in_data, dict): + return cls.__ref6570_dict_expansion( + variable_name, + in_data, + explode, + percent_encode, + prefix_separator_iterator, + var_name_piece, + named_parameter_expansion + ) + # bool, bytes, etc + raise exceptions.ApiValueError('Unable to generate a ref6570 representation of {}'.format(in_data)) + + +class StyleFormSerializer(ParameterSerializerBase): + @classmethod + def _serialize_form( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + name: str, + explode: bool, + percent_encode: bool, + prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator] = None + ) -> str: + if prefix_separator_iterator is None: + prefix_separator_iterator = PrefixSeparatorIterator('', '&') + return cls._ref6570_expansion( + variable_name=name, + in_data=in_data, + explode=explode, + percent_encode=percent_encode, + prefix_separator_iterator=prefix_separator_iterator + ) + + +class StyleSimpleSerializer(ParameterSerializerBase): + + @classmethod + def _serialize_simple( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + name: str, + explode: bool, + percent_encode: bool + ) -> str: + prefix_separator_iterator = PrefixSeparatorIterator('', ',') + return cls._ref6570_expansion( + variable_name=name, + in_data=in_data, + explode=explode, + percent_encode=percent_encode, + prefix_separator_iterator=prefix_separator_iterator + ) + + @classmethod + def _deserialize_simple( + cls, + in_data: str, + name: str, + explode: bool, + percent_encode: bool + ) -> typing.Union[str, typing.List[str], typing.Dict[str, str]]: + raise NotImplementedError( + "Deserialization of style=simple has not yet been added. " + "If you need this how about you submit a PR adding it?" + ) + + +class JSONDetector: + """ + Works for: + application/json + application/json; charset=UTF-8 + application/json-patch+json + application/geo+json + """ + __json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*") + + @classmethod + def _content_type_is_json(cls, content_type: str) -> bool: + if cls.__json_content_type_pattern.match(content_type): + return True + return False + + +class Encoding: + content_type: str + headers: typing.Optional[typing.Dict[str, 'HeaderParameter']] = None + style: typing.Optional[ParameterStyle] = None + explode: bool = False + allow_reserved: bool = False + + +class MediaType: + """ + Used to store request and response body schema information + encoding: + A map between a property name and its encoding information. + The key, being the property name, MUST exist in the schema as a property. + The encoding object SHALL only apply to requestBody objects when the media type is + multipart or application/x-www-form-urlencoded. + """ + schema: typing.Optional[typing.Type[schemas.Schema]] = None + encoding: typing.Optional[typing.Dict[str, Encoding]] = None + + +class ParameterBase(JSONDetector): + in_type: ParameterInType + required: bool + style: typing.Optional[ParameterStyle] + explode: typing.Optional[bool] + allow_reserved: typing.Optional[bool] + schema: typing.Optional[typing.Type[schemas.Schema]] + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] + + _json_encoder = JSONEncoder() + + def __init_subclass__(cls, **kwargs): + if cls.explode is None: + if cls.style is ParameterStyle.FORM: + cls.explode = True + else: + cls.explode = False + + @classmethod + def _serialize_json( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + eliminate_whitespace: bool = False + ) -> str: + if eliminate_whitespace: + return json.dumps(in_data, separators=cls._json_encoder.compact_separators) + return json.dumps(in_data) + +_SERIALIZE_TYPES = typing.Union[ + int, + float, + str, + datetime.date, + datetime.datetime, + None, + bool, + list, + tuple, + dict, + schemas.immutabledict +] + +_JSON_TYPES = typing.Union[ + int, + float, + str, + None, + bool, + typing.Tuple['_JSON_TYPES', ...], + schemas.immutabledict[str, '_JSON_TYPES'], +] + +@dataclasses.dataclass +class PathParameter(ParameterBase, StyleSimpleSerializer): + name: str + required: bool = False + in_type: ParameterInType = ParameterInType.PATH + style: ParameterStyle = ParameterStyle.SIMPLE + explode: bool = False + allow_reserved: typing.Optional[bool] = None + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + + @classmethod + def __serialize_label( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list] + ) -> typing.Dict[str, str]: + prefix_separator_iterator = PrefixSeparatorIterator('.', '.') + value = cls._ref6570_expansion( + variable_name=cls.name, + in_data=in_data, + explode=cls.explode, + percent_encode=True, + prefix_separator_iterator=prefix_separator_iterator + ) + return cls._to_dict(cls.name, value) + + @classmethod + def __serialize_matrix( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list] + ) -> typing.Dict[str, str]: + prefix_separator_iterator = PrefixSeparatorIterator(';', ';') + value = cls._ref6570_expansion( + variable_name=cls.name, + in_data=in_data, + explode=cls.explode, + percent_encode=True, + prefix_separator_iterator=prefix_separator_iterator + ) + return cls._to_dict(cls.name, value) + + @classmethod + def __serialize_simple( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + ) -> typing.Dict[str, str]: + value = cls._serialize_simple( + in_data=in_data, + name=cls.name, + explode=cls.explode, + percent_encode=True + ) + return cls._to_dict(cls.name, value) + + @classmethod + def serialize( + cls, + in_data: _SERIALIZE_TYPES, + skip_validation: bool = False + ) -> typing.Dict[str, str]: + if cls.schema: + cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + """ + simple -> path + path: + returns path_params: dict + label -> path + returns path_params + matrix -> path + returns path_params + """ + if cls.style: + if cls.style is ParameterStyle.SIMPLE: + return cls.__serialize_simple(cast_in_data) + elif cls.style is ParameterStyle.LABEL: + return cls.__serialize_label(cast_in_data) + elif cls.style is ParameterStyle.MATRIX: + return cls.__serialize_matrix(cast_in_data) + assert cls.content is not None + for content_type, media_type in cls.content.items(): + assert media_type.schema is not None + cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + if cls._content_type_is_json(content_type): + value = cls._serialize_json(cast_in_data) + return cls._to_dict(cls.name, value) + else: + raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) + raise ValueError('Invalid value for content, it was empty and must have 1 key value pair') + + +@dataclasses.dataclass +class QueryParameter(ParameterBase, StyleFormSerializer): + name: str + required: bool = False + in_type: ParameterInType = ParameterInType.QUERY + style: ParameterStyle = ParameterStyle.FORM + explode: typing.Optional[bool] = None + allow_reserved: typing.Optional[bool] = None + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + + @classmethod + def __serialize_space_delimited( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator], + explode: bool + ) -> typing.Dict[str, str]: + if prefix_separator_iterator is None: + prefix_separator_iterator = cls.get_prefix_separator_iterator() + value = cls._ref6570_expansion( + variable_name=cls.name, + in_data=in_data, + explode=explode, + percent_encode=True, + prefix_separator_iterator=prefix_separator_iterator + ) + return cls._to_dict(cls.name, value) + + @classmethod + def __serialize_pipe_delimited( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator], + explode: bool + ) -> typing.Dict[str, str]: + if prefix_separator_iterator is None: + prefix_separator_iterator = cls.get_prefix_separator_iterator() + value = cls._ref6570_expansion( + variable_name=cls.name, + in_data=in_data, + explode=explode, + percent_encode=True, + prefix_separator_iterator=prefix_separator_iterator + ) + return cls._to_dict(cls.name, value) + + @classmethod + def __serialize_form( + cls, + in_data: typing.Union[None, int, float, str, bool, dict, list], + prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator], + explode: bool + ) -> typing.Dict[str, str]: + if prefix_separator_iterator is None: + prefix_separator_iterator = cls.get_prefix_separator_iterator() + value = cls._serialize_form( + in_data, + name=cls.name, + explode=explode, + percent_encode=True, + prefix_separator_iterator=prefix_separator_iterator + ) + return cls._to_dict(cls.name, value) + + @classmethod + def get_prefix_separator_iterator(cls) -> PrefixSeparatorIterator: + if cls.style is ParameterStyle.FORM: + return PrefixSeparatorIterator('?', '&') + elif cls.style is ParameterStyle.SPACE_DELIMITED: + return PrefixSeparatorIterator('', '%20') + elif cls.style is ParameterStyle.PIPE_DELIMITED: + return PrefixSeparatorIterator('', '|') + raise ValueError(f'No iterator possible for style={cls.style}') + + @classmethod + def serialize( + cls, + in_data: _SERIALIZE_TYPES, + prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator] = None, + skip_validation: bool = False + ) -> typing.Dict[str, str]: + if cls.schema: + cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + """ + form -> query + query: + - GET/HEAD/DELETE: could use fields + - PUT/POST: must use urlencode to send parameters + returns fields: tuple + spaceDelimited -> query + returns fields + pipeDelimited -> query + returns fields + deepObject -> query, https://github.com/OAI/OpenAPI-Specification/issues/1706 + returns fields + """ + if cls.style: + # TODO update query ones to omit setting values when [] {} or None is input + explode = cls.explode if cls.explode is not None else cls.style == ParameterStyle.FORM + if cls.style is ParameterStyle.FORM: + return cls.__serialize_form(cast_in_data, prefix_separator_iterator, explode) + elif cls.style is ParameterStyle.SPACE_DELIMITED: + return cls.__serialize_space_delimited(cast_in_data, prefix_separator_iterator, explode) + elif cls.style is ParameterStyle.PIPE_DELIMITED: + return cls.__serialize_pipe_delimited(cast_in_data, prefix_separator_iterator, explode) + if prefix_separator_iterator is None: + prefix_separator_iterator = cls.get_prefix_separator_iterator() + assert cls.content is not None + for content_type, media_type in cls.content.items(): + assert media_type.schema is not None + cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + if cls._content_type_is_json(content_type): + value = cls._serialize_json(cast_in_data, eliminate_whitespace=True) + return cls._to_dict( + cls.name, + next(prefix_separator_iterator) + cls.name + '=' + parse.quote(value) + ) + else: + raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) + raise ValueError('Invalid value for content, it was empty and must have 1 key value pair') + + +@dataclasses.dataclass +class CookieParameter(ParameterBase, StyleFormSerializer): + name: str + required: bool = False + style: ParameterStyle = ParameterStyle.FORM + in_type: ParameterInType = ParameterInType.COOKIE + explode: typing.Optional[bool] = None + allow_reserved: typing.Optional[bool] = None + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + + @classmethod + def serialize( + cls, + in_data: _SERIALIZE_TYPES, + skip_validation: bool = False + ) -> typing.Dict[str, str]: + if cls.schema: + cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + """ + form -> cookie + returns fields: tuple + """ + if cls.style: + """ + TODO add escaping of comma, space, equals + or turn encoding on + """ + explode = cls.explode if cls.explode is not None else cls.style == ParameterStyle.FORM + value = cls._serialize_form( + cast_in_data, + explode=explode, + name=cls.name, + percent_encode=False, + prefix_separator_iterator=PrefixSeparatorIterator('', '&') + ) + return cls._to_dict(cls.name, value) + assert cls.content is not None + for content_type, media_type in cls.content.items(): + assert media_type.schema is not None + cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + if cls._content_type_is_json(content_type): + value = cls._serialize_json(cast_in_data) + return cls._to_dict(cls.name, value) + else: + raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) + raise ValueError('Invalid value for content, it was empty and must have 1 key value pair') + + +class __HeaderParameterBase(ParameterBase, StyleSimpleSerializer): + style: ParameterStyle = ParameterStyle.SIMPLE + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + explode: bool = False + + @staticmethod + def __to_headers(in_data: typing.Tuple[typing.Tuple[str, str], ...]) -> _collections.HTTPHeaderDict: + data = tuple(t for t in in_data if t) + headers = _collections.HTTPHeaderDict() + if not data: + return headers + headers.extend(data) + return headers + + @classmethod + def serialize_with_name( + cls, + in_data: _SERIALIZE_TYPES, + name: str, + skip_validation: bool = False + ) -> _collections.HTTPHeaderDict: + if cls.schema: + cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + """ + simple -> header + headers: PoolManager needs a mapping, tuple is close + returns headers: dict + """ + if cls.style: + value = cls._serialize_simple(cast_in_data, name, cls.explode, False) + return cls.__to_headers(((name, value),)) + assert cls.content is not None + for content_type, media_type in cls.content.items(): + assert media_type.schema is not None + cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data) + cast_in_data = cls._json_encoder.default(cast_in_data) + if cls._content_type_is_json(content_type): + value = cls._serialize_json(cast_in_data) + return cls.__to_headers(((name, value),)) + else: + raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) + raise ValueError('Invalid value for content, it was empty and must have 1 key value pair') + + @classmethod + def deserialize( + cls, + in_data: str, + name: str + ): + if cls.schema: + """ + simple -> header + headers: PoolManager needs a mapping, tuple is close + returns headers: dict + """ + if cls.style: + extracted_data = cls._deserialize_simple(in_data, name, cls.explode, False) + return cls.schema.validate_base(extracted_data) + assert cls.content is not None + for content_type, media_type in cls.content.items(): + if cls._content_type_is_json(content_type): + cast_in_data: typing.Union[dict, list, None, int, float, str] = json.loads(in_data) + assert media_type.schema is not None + return media_type.schema.validate_base(cast_in_data) + else: + raise NotImplementedError('Deserialization of {} has not yet been implemented'.format(content_type)) + raise ValueError('Invalid value for content, it was empty and must have 1 key value pair') + + +class HeaderParameterWithoutName(__HeaderParameterBase): + required: bool = False + style: ParameterStyle = ParameterStyle.SIMPLE + in_type: ParameterInType = ParameterInType.HEADER + explode: bool = False + allow_reserved: typing.Optional[bool] = None + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + + @classmethod + def serialize( + cls, + in_data: _SERIALIZE_TYPES, + name: str, + skip_validation: bool = False + ) -> _collections.HTTPHeaderDict: + return cls.serialize_with_name( + in_data, + name, + skip_validation=skip_validation + ) + + +class HeaderParameter(__HeaderParameterBase): + name: str + required: bool = False + style: ParameterStyle = ParameterStyle.SIMPLE + in_type: ParameterInType = ParameterInType.HEADER + explode: bool = False + allow_reserved: typing.Optional[bool] = None + schema: typing.Optional[typing.Type[schemas.Schema]] = None + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + + @classmethod + def serialize( + cls, + in_data: _SERIALIZE_TYPES, + skip_validation: bool = False + ) -> _collections.HTTPHeaderDict: + return cls.serialize_with_name( + in_data, + cls.name, + skip_validation=skip_validation + ) + +T = typing.TypeVar("T", bound=api_response.ApiResponse) + + +class OpenApiResponse(typing.Generic[T], JSONDetector, abc.ABC): + __filename_content_disposition_pattern = re.compile('filename="(.+?)"') + content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None + headers: typing.Optional[typing.Dict[str, typing.Type[HeaderParameterWithoutName]]] = None + headers_schema: typing.Optional[typing.Type[schemas.Schema]] = None + + @classmethod + @abc.abstractmethod + def get_response(cls, response, headers, body) -> T: ... + + @staticmethod + def __deserialize_json(response: urllib3.HTTPResponse) -> typing.Any: + # python must be >= 3.9 so we can pass in bytes into json.loads + return json.loads(response.data) + + @staticmethod + def __file_name_from_response_url(response_url: typing.Optional[str]) -> typing.Optional[str]: + if response_url is None: + return None + url_path = parse.urlparse(response_url).path + if url_path: + path_basename = os.path.basename(url_path) + if path_basename: + _filename, ext = os.path.splitext(path_basename) + if ext: + return path_basename + return None + + @classmethod + def __file_name_from_content_disposition(cls, content_disposition: typing.Optional[str]) -> typing.Optional[str]: + if content_disposition is None: + return None + match = cls.__filename_content_disposition_pattern.search(content_disposition) + if not match: + return None + return match.group(1) + + @classmethod + def __deserialize_application_octet_stream( + cls, response: urllib3.HTTPResponse + ) -> typing.Union[bytes, io.BufferedReader]: + """ + urllib3 use cases: + 1. when preload_content=True (stream=False) then supports_chunked_reads is False and bytes are returned + 2. when preload_content=False (stream=True) then supports_chunked_reads is True and + a file will be written and returned + """ + if response.supports_chunked_reads(): + file_name = ( + cls.__file_name_from_content_disposition(response.headers.get('content-disposition')) + or cls.__file_name_from_response_url(response.geturl()) + ) + + if file_name is None: + _fd, path = tempfile.mkstemp() + else: + path = os.path.join(tempfile.gettempdir(), file_name) + + with open(path, 'wb') as write_file: + chunk_size = 1024 + while True: + data = response.read(chunk_size) + if not data: + break + write_file.write(data) + # release_conn is needed for streaming connections only + response.release_conn() + new_file = open(path, 'rb') + return new_file + else: + return response.data + + @staticmethod + def __deserialize_multipart_form_data( + response: urllib3.HTTPResponse + ) -> typing.Dict[str, typing.Any]: + msg = email.message_from_bytes(response.data) + return { + part.get_param("name", header="Content-Disposition"): part.get_payload( + decode=True + ).decode(part.get_content_charset()) + if part.get_content_charset() + else part.get_payload() + for part in msg.get_payload() + } + + @classmethod + def deserialize(cls, response: urllib3.HTTPResponse, configuration: schema_configuration_.SchemaConfiguration) -> T: + content_type = response.headers.get('content-type') + deserialized_body = schemas.unset + streamed = response.supports_chunked_reads() + + deserialized_headers: typing.Union[schemas.Unset, typing.Dict[str, typing.Any]] = schemas.unset + if cls.headers is not None and cls.headers_schema is not None: + deserialized_headers = {} + for header_name, header_param in cls.headers.items(): + header_value = response.headers.get(header_name) + if header_value is None: + continue + header_value = header_param.deserialize(header_value, header_name) + deserialized_headers[header_name] = header_value + deserialized_headers = cls.headers_schema.validate_base(deserialized_headers, configuration=configuration) + + if cls.content is not None: + if content_type not in cls.content: + raise exceptions.ApiValueError( + f"Invalid content_type returned. Content_type='{content_type}' was returned " + f"when only {str(set(cls.content))} are defined for status_code={str(response.status)}" + ) + body_schema = cls.content[content_type].schema + if body_schema is None: + # some specs do not define response content media type schemas + return cls.get_response( + response=response, + headers=deserialized_headers, + body=schemas.unset + ) + + if cls._content_type_is_json(content_type): + body_data = cls.__deserialize_json(response) + elif content_type == 'application/octet-stream': + body_data = cls.__deserialize_application_octet_stream(response) + elif content_type.startswith('multipart/form-data'): + body_data = cls.__deserialize_multipart_form_data(response) + content_type = 'multipart/form-data' + elif content_type == 'application/x-pem-file': + body_data = response.data.decode() + else: + raise NotImplementedError('Deserialization of {} has not yet been implemented'.format(content_type)) + body_schema = schemas.get_class(body_schema) + if body_schema is schemas.BinarySchema: + deserialized_body = body_schema.validate_base(body_data) + else: + deserialized_body = body_schema.validate_base( + body_data, configuration=configuration) + elif streamed: + response.release_conn() + + return cls.get_response( + response=response, + headers=deserialized_headers, + body=deserialized_body + ) + + +@dataclasses.dataclass class ApiClient: - pass + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + Do not edit the class manually. + + :param configuration: api_configuration.ApiConfiguration object for this client + :param schema_configuration: schema_configuration_.SchemaConfiguration object for this client + :param default_headers: any default headers to include when making calls to the API. + :param pool_threads: The number of threads to use for async requests + to the API. More threads means more concurrent API requests. + """ + configuration: api_configuration.ApiConfiguration = dataclasses.field( + default_factory=lambda: api_configuration.ApiConfiguration()) + schema_configuration: schema_configuration_.SchemaConfiguration = dataclasses.field( + default_factory=lambda: schema_configuration_.SchemaConfiguration()) + default_headers: _collections.HTTPHeaderDict = dataclasses.field( + default_factory=lambda: _collections.HTTPHeaderDict()) + pool_threads: int = 1 + user_agent: str = 'OpenAPI-JSON-Schema-Generator/1.0.0/python' + rest_client: rest.RESTClientObject = dataclasses.field(init=False) + + def __post_init__(self): + self._pool = None + self.rest_client = rest.RESTClientObject(self.configuration) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + if self._pool: + self._pool.close() + self._pool.join() + self._pool = None + if hasattr(atexit, 'unregister'): + atexit.unregister(self.close) + + @property + def pool(self): + """Create thread pool on first request + avoids instantiating unused threadpool for blocking clients. + """ + if self._pool is None: + atexit.register(self.close) + self._pool = pool.ThreadPool(self.pool_threads) + return self._pool + + def set_default_header(self, header_name: str, header_value: str): + self.default_headers[header_name] = header_value + + def call_api( + self, + resource_path: str, + method: str, + host: str, + query_params_suffix: typing.Optional[str] = None, + headers: typing.Optional[_collections.HTTPHeaderDict] = None, + body: typing.Union[str, bytes, None] = None, + fields: typing.Optional[typing.Tuple[rest.RequestField, ...]] = None, + security_requirement_object: typing.Optional[security_schemes.SecurityRequirementObject] = None, + stream: bool = False, + timeout: typing.Union[int, float, typing.Tuple, None] = None, + ) -> urllib3.HTTPResponse: + """Makes the HTTP request (synchronous) and returns deserialized data. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param headers: Header parameters to be + placed in the request header. + :param body: Request body. + :param fields: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data` + :param security_requirement_object: The security requirement object, used to apply auth when making the call + :param async_req: execute request asynchronously + :param stream: if True, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Also when True, if the openapi spec describes a file download, + the data will be written to a local filesystem file and the schemas.BinarySchema + instance will also inherit from FileSchema and schemas.FileIO + Default is False. + :type stream: bool, optional + :param timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param host: api endpoint host + :return: + the method will return the response directly. + """ + # header parameters + used_headers = _collections.HTTPHeaderDict(self.default_headers) + user_agent_key = 'User-Agent' + if user_agent_key not in used_headers and self.user_agent: + used_headers[user_agent_key] = self.user_agent + + # auth setting + self.update_params_for_auth( + used_headers, + security_requirement_object, + resource_path, + method, + body, + query_params_suffix + ) + + # must happen after auth setting in case user is overriding those + if headers: + used_headers.update(headers) + + # request url + url = host + resource_path + if query_params_suffix: + url += query_params_suffix + + # perform request and return response + response = self.request( + method, + url, + headers=used_headers, + fields=fields, + body=body, + stream=stream, + timeout=timeout, + ) + return response + + def request( + self, + method: str, + url: str, + headers: typing.Optional[_collections.HTTPHeaderDict] = None, + fields: typing.Optional[typing.Tuple[rest.RequestField, ...]] = None, + body: typing.Union[str, bytes, None] = None, + stream: bool = False, + timeout: typing.Union[int, float, typing.Tuple, None] = None, + ) -> urllib3.HTTPResponse: + """Makes the HTTP request using RESTClient.""" + if method == "get": + return self.rest_client.get(url, + stream=stream, + timeout=timeout, + headers=headers) + elif method == "head": + return self.rest_client.head(url, + stream=stream, + timeout=timeout, + headers=headers) + elif method == "options": + return self.rest_client.options(url, + headers=headers, + fields=fields, + stream=stream, + timeout=timeout, + body=body) + elif method == "post": + return self.rest_client.post(url, + headers=headers, + fields=fields, + stream=stream, + timeout=timeout, + body=body) + elif method == "put": + return self.rest_client.put(url, + headers=headers, + fields=fields, + stream=stream, + timeout=timeout, + body=body) + elif method == "patch": + return self.rest_client.patch(url, + headers=headers, + fields=fields, + stream=stream, + timeout=timeout, + body=body) + elif method == "delete": + return self.rest_client.delete(url, + headers=headers, + stream=stream, + timeout=timeout, + body=body) + else: + raise exceptions.ApiValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def update_params_for_auth( + self, + headers: _collections.HTTPHeaderDict, + security_requirement_object: typing.Optional[security_schemes.SecurityRequirementObject], + resource_path: str, + method: str, + body: typing.Union[str, bytes, None] = None, + query_params_suffix: typing.Optional[str] = None + ): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param security_requirement_object: the openapi security requirement object + :param resource_path: A string representation of the HTTP request resource path. + :param method: A string representation of the HTTP request method. + :param body: A object representing the body of the HTTP request. + The object type is the return value of _encoder.default(). + """ + return + +@dataclasses.dataclass +class Api: + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + api_client: ApiClient = dataclasses.field(default_factory=lambda: ApiClient()) + + @staticmethod + def _get_used_path( + used_path: str, + path_parameters: typing.Tuple[typing.Type[PathParameter], ...] = (), + path_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None, + query_parameters: typing.Tuple[typing.Type[QueryParameter], ...] = (), + query_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None, + skip_validation: bool = False + ) -> typing.Tuple[str, str]: + used_path_params = {} + if path_params is not None: + for path_parameter in path_parameters: + parameter_data = path_params.get(path_parameter.name, schemas.unset) + if isinstance(parameter_data, schemas.Unset): + continue + assert not isinstance(parameter_data, (bytes, schemas.FileIO)) + serialized_data = path_parameter.serialize(parameter_data, skip_validation=skip_validation) + used_path_params.update(serialized_data) + + for k, v in used_path_params.items(): + used_path = used_path.replace('{%s}' % k, v) + + query_params_suffix = "" + if query_params is not None: + prefix_separator_iterator = None + for query_parameter in query_parameters: + parameter_data = query_params.get(query_parameter.name, schemas.unset) + if isinstance(parameter_data, schemas.Unset): + continue + if prefix_separator_iterator is None: + prefix_separator_iterator = query_parameter.get_prefix_separator_iterator() + assert not isinstance(parameter_data, (bytes, schemas.FileIO)) + serialized_data = query_parameter.serialize( + parameter_data, + prefix_separator_iterator=prefix_separator_iterator, + skip_validation=skip_validation + ) + for serialized_value in serialized_data.values(): + query_params_suffix += serialized_value + return used_path, query_params_suffix + + @staticmethod + def _get_headers( + header_parameters: typing.Tuple[typing.Type[HeaderParameter], ...] = (), + header_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None, + accept_content_types: typing.Tuple[str, ...] = (), + skip_validation: bool = False + ) -> _collections.HTTPHeaderDict: + headers = _collections.HTTPHeaderDict() + if header_params is not None: + for parameter in header_parameters: + parameter_data = header_params.get(parameter.name, schemas.unset) + if isinstance(parameter_data, schemas.Unset): + continue + assert not isinstance(parameter_data, (bytes, schemas.FileIO)) + serialized_data = parameter.serialize(parameter_data, skip_validation=skip_validation) + headers.extend(serialized_data) + if accept_content_types: + for accept_content_type in accept_content_types: + headers.add('Accept', accept_content_type) + return headers + + def _get_fields_and_body( + self, + request_body: typing.Type[RequestBody], + body: typing.Union[schemas.INPUT_TYPES_ALL, schemas.Unset], + content_type: str, + headers: _collections.HTTPHeaderDict + ): + if request_body.required and body is schemas.unset: + raise exceptions.ApiValueError( + 'The required body parameter has an invalid value of: unset. Set a valid value instead') + + if isinstance(body, schemas.Unset): + return None, None + + serialized_fields = None + serialized_body = None + serialized_data = request_body.serialize(body, content_type, configuration=self.api_client.schema_configuration) + headers.add('Content-Type', content_type) + if 'fields' in serialized_data: + serialized_fields = serialized_data['fields'] + elif 'body' in serialized_data: + serialized_body = serialized_data['body'] + return serialized_fields, serialized_body + + @staticmethod + def _verify_response_status(response: api_response.ApiResponse): + if not 200 <= response.response.status <= 399: + raise exceptions.ApiException( + status=response.response.status, + reason=response.response.reason, + api_response=response + ) + + +class SerializedRequestBody(typing.TypedDict, total=False): + body: typing.Union[str, bytes] + fields: typing.Tuple[rest.RequestField, ...] + + +class RequestBody(StyleFormSerializer, JSONDetector): + """ + A request body parameter + content: content_type to MediaType schemas.Schema info + """ + __json_encoder = JSONEncoder() + __plain_txt_content_types = {'text/plain', 'application/x-pem-file'} + content: typing.Dict[str, typing.Type[MediaType]] + required: bool = False + + @classmethod + def __serialize_json( + cls, + in_data: _JSON_TYPES + ) -> SerializedRequestBody: + in_data = cls.__json_encoder.default(in_data) + json_str = json.dumps(in_data, separators=(",", ":"), ensure_ascii=False).encode( + "utf-8" + ) + return {'body': json_str} + + @staticmethod + def __serialize_text_plain(in_data: typing.Union[int, float, str]) -> SerializedRequestBody: + return {'body': str(in_data)} + + @classmethod + def __multipart_json_item(cls, key: str, value: _JSON_TYPES) -> rest.RequestField: + json_value = cls.__json_encoder.default(value) + request_field = rest.RequestField(name=key, data=json.dumps(json_value)) + request_field.make_multipart(content_type='application/json') + return request_field + + @classmethod + def __multipart_form_item(cls, key: str, value: typing.Union[_JSON_TYPES, bytes, schemas.FileIO]) -> rest.RequestField: + if isinstance(value, str): + request_field = rest.RequestField(name=key, data=str(value)) + request_field.make_multipart(content_type='text/plain') + elif isinstance(value, bytes): + request_field = rest.RequestField(name=key, data=value) + request_field.make_multipart(content_type='application/octet-stream') + elif isinstance(value, schemas.FileIO): + # TODO use content.encoding to limit allowed content types if they are present + urllib3_request_field = rest.RequestField.from_tuples(key, (os.path.basename(str(value.name)), value.read())) + request_field = typing.cast(rest.RequestField, urllib3_request_field) + value.close() + else: + request_field = cls.__multipart_json_item(key=key, value=value) + return request_field + + @classmethod + def __serialize_multipart_form_data( + cls, in_data: schemas.immutabledict[str, typing.Union[_JSON_TYPES, bytes, schemas.FileIO]] + ) -> SerializedRequestBody: + """ + In a multipart/form-data request body, each schema property, or each element of a schema array property, + takes a section in the payload with an internal header as defined by RFC7578. The serialization strategy + for each property of a multipart/form-data request body can be specified in an associated Encoding Object. + + When passing in multipart types, boundaries MAY be used to separate sections of the content being + transferred – thus, the following default Content-Types are defined for multipart: + + If the (object) property is a primitive, or an array of primitive values, the default Content-Type is text/plain + If the property is complex, or an array of complex values, the default Content-Type is application/json + Question: how is the array of primitives encoded? + If the property is a type: string with a contentEncoding, the default Content-Type is application/octet-stream + """ + fields = [] + for key, value in in_data.items(): + if isinstance(value, tuple): + if value: + # values use explode = True, so the code makes a rest.RequestField for each item with name=key + for item in value: + request_field = cls.__multipart_form_item(key=key, value=item) + fields.append(request_field) + else: + # send an empty array as json because exploding will not send it + request_field = cls.__multipart_json_item(key=key, value=value) # type: ignore + fields.append(request_field) + else: + request_field = cls.__multipart_form_item(key=key, value=value) + fields.append(request_field) + + return {'fields': tuple(fields)} + + @staticmethod + def __serialize_application_octet_stream(in_data: typing.Union[schemas.FileIO, bytes]) -> SerializedRequestBody: + if isinstance(in_data, bytes): + return {'body': in_data} + # schemas.FileIO type + used_in_data = in_data.read() + in_data.close() + return {'body': used_in_data} + + @classmethod + def __serialize_application_x_www_form_data( + cls, in_data: schemas.immutabledict[str, _JSON_TYPES] + ) -> SerializedRequestBody: + """ + POST submission of form data in body + """ + cast_in_data = cls.__json_encoder.default(in_data) + value = cls._serialize_form(cast_in_data, name='', explode=True, percent_encode=True) + return {'body': value} + + @classmethod + def serialize( + cls, in_data: schemas.INPUT_TYPES_ALL, content_type: str, configuration: typing.Optional[schema_configuration_.SchemaConfiguration] = None + ) -> SerializedRequestBody: + """ + If a str is returned then the result will be assigned to data when making the request + If a tuple is returned then the result will be used as fields input in encode_multipart_formdata + Return a tuple of + + The key of the return dict is + - body for application/json + - encode_multipart and fields for multipart/form-data + """ + media_type = cls.content[content_type] + assert media_type.schema is not None + schema = schemas.get_class(media_type.schema) + used_configuration = configuration if configuration is not None else schema_configuration_.SchemaConfiguration() + cast_in_data = schema.validate_base(in_data, configuration=used_configuration) + # TODO check for and use encoding if it exists + # and content_type is multipart or application/x-www-form-urlencoded + if cls._content_type_is_json(content_type): + if isinstance(cast_in_data, (schemas.FileIO, bytes)): + raise ValueError(f"Invalid input data type. Data must be int/float/str/bool/None/tuple/immutabledict and it was type {type(cast_in_data)}") + return cls.__serialize_json(cast_in_data) + elif content_type in cls.__plain_txt_content_types: + if not isinstance(cast_in_data, (int, float, str)): + raise ValueError(f"Unable to serialize type {type(cast_in_data)} to text/plain") + return cls.__serialize_text_plain(cast_in_data) + elif content_type == 'multipart/form-data': + if not isinstance(cast_in_data, schemas.immutabledict): + raise ValueError(f"Unable to serialize {cast_in_data} to multipart/form-data because it is not a dict of data") + return cls.__serialize_multipart_form_data(cast_in_data) + elif content_type == 'application/x-www-form-urlencoded': + if not isinstance(cast_in_data, schemas.immutabledict): + raise ValueError( + f"Unable to serialize {cast_in_data} to application/x-www-form-urlencoded because it is not a dict of data") + return cls.__serialize_application_x_www_form_data(cast_in_data) + elif content_type == 'application/octet-stream': + if not isinstance(cast_in_data, (schemas.FileIO, bytes)): + raise ValueError(f"Invalid input data type. Data must be bytes or File for content_type={content_type}") + return cls.__serialize_application_octet_stream(cast_in_data) + raise NotImplementedError('Serialization has not yet been implemented for {}'.format(content_type)) diff --git a/comfy/api/api_response.py b/comfy/api/api_response.py new file mode 100644 index 000000000..1e9c6ee27 --- /dev/null +++ b/comfy/api/api_response.py @@ -0,0 +1,28 @@ +# coding: utf-8 +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import dataclasses +import typing + +import urllib3 + +from comfy.api import schemas + + +@dataclasses.dataclass(frozen=True) +class ApiResponse: + response: urllib3.HTTPResponse + body: typing.Union[schemas.Unset, schemas.OUTPUT_BASE_TYPES] + headers: typing.Union[schemas.Unset, typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] + + +@dataclasses.dataclass(frozen=True) +class ApiResponseWithoutDeserialization(ApiResponse): + response: urllib3.HTTPResponse + body: schemas.Unset = schemas.unset + headers: schemas.Unset = schemas.unset diff --git a/comfy/api/apis/__init__.py b/comfy/api/apis/__init__.py new file mode 100644 index 000000000..7840f7726 --- /dev/null +++ b/comfy/api/apis/__init__.py @@ -0,0 +1,3 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints then import them from +# tags, paths, or path_to_api, or tag_to_api \ No newline at end of file diff --git a/comfy/api/apis/path_to_api.py b/comfy/api/apis/path_to_api.py new file mode 100644 index 000000000..d81c2dcf4 --- /dev/null +++ b/comfy/api/apis/path_to_api.py @@ -0,0 +1,50 @@ +import typing +import typing_extensions + +from comfy.api.apis.paths.solidus import Solidus +from comfy.api.apis.paths.api_v1_images_digest import ApiV1ImagesDigest +from comfy.api.apis.paths.api_v1_prompts import ApiV1Prompts +from comfy.api.apis.paths.embeddings import Embeddings +from comfy.api.apis.paths.extensions import Extensions +from comfy.api.apis.paths.history import History +from comfy.api.apis.paths.interrupt import Interrupt +from comfy.api.apis.paths.object_info import ObjectInfo +from comfy.api.apis.paths.prompt import Prompt +from comfy.api.apis.paths.queue import Queue +from comfy.api.apis.paths.upload_image import UploadImage +from comfy.api.apis.paths.view import View + +PathToApi = typing.TypedDict( + 'PathToApi', + { + "/": typing.Type[Solidus], + "/api/v1/images/{digest}": typing.Type[ApiV1ImagesDigest], + "/api/v1/prompts": typing.Type[ApiV1Prompts], + "/embeddings": typing.Type[Embeddings], + "/extensions": typing.Type[Extensions], + "/history": typing.Type[History], + "/interrupt": typing.Type[Interrupt], + "/object_info": typing.Type[ObjectInfo], + "/prompt": typing.Type[Prompt], + "/queue": typing.Type[Queue], + "/upload/image": typing.Type[UploadImage], + "/view": typing.Type[View], + } +) + +path_to_api = PathToApi( + { + "/": Solidus, + "/api/v1/images/{digest}": ApiV1ImagesDigest, + "/api/v1/prompts": ApiV1Prompts, + "/embeddings": Embeddings, + "/extensions": Extensions, + "/history": History, + "/interrupt": Interrupt, + "/object_info": ObjectInfo, + "/prompt": Prompt, + "/queue": Queue, + "/upload/image": UploadImage, + "/view": View, + } +) diff --git a/comfy/api/apis/paths/__init__.py b/comfy/api/apis/paths/__init__.py new file mode 100644 index 000000000..49a7a8c43 --- /dev/null +++ b/comfy/api/apis/paths/__init__.py @@ -0,0 +1,3 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.path_to_api import path_to_api \ No newline at end of file diff --git a/comfy/api/apis/paths/api_v1_images_digest.py b/comfy/api/apis/paths/api_v1_images_digest.py new file mode 100644 index 000000000..e280e304e --- /dev/null +++ b/comfy/api/apis/paths/api_v1_images_digest.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.api_v1_images_digest.get.operation import ApiForGet + + +class ApiV1ImagesDigest( + ApiForGet, +): + pass diff --git a/comfy/api/apis/paths/api_v1_prompts.py b/comfy/api/apis/paths/api_v1_prompts.py new file mode 100644 index 000000000..1aafdff3c --- /dev/null +++ b/comfy/api/apis/paths/api_v1_prompts.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.api_v1_prompts.get.operation import ApiForGet +from comfy.api.paths.api_v1_prompts.post.operation import ApiForPost + + +class ApiV1Prompts( + ApiForGet, + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/embeddings.py b/comfy/api/apis/paths/embeddings.py new file mode 100644 index 000000000..a0ad8d79b --- /dev/null +++ b/comfy/api/apis/paths/embeddings.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.embeddings.get.operation import ApiForGet + + +class Embeddings( + ApiForGet, +): + pass diff --git a/comfy/api/apis/paths/extensions.py b/comfy/api/apis/paths/extensions.py new file mode 100644 index 000000000..cf4e88ac4 --- /dev/null +++ b/comfy/api/apis/paths/extensions.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.extensions.get.operation import ApiForGet + + +class Extensions( + ApiForGet, +): + pass diff --git a/comfy/api/apis/paths/history.py b/comfy/api/apis/paths/history.py new file mode 100644 index 000000000..9d2917cbe --- /dev/null +++ b/comfy/api/apis/paths/history.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.history.get.operation import ApiForGet +from comfy.api.paths.history.post.operation import ApiForPost + + +class History( + ApiForGet, + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/interrupt.py b/comfy/api/apis/paths/interrupt.py new file mode 100644 index 000000000..29967efbc --- /dev/null +++ b/comfy/api/apis/paths/interrupt.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.interrupt.post.operation import ApiForPost + + +class Interrupt( + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/object_info.py b/comfy/api/apis/paths/object_info.py new file mode 100644 index 000000000..78e780ed4 --- /dev/null +++ b/comfy/api/apis/paths/object_info.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.object_info.get.operation import ApiForGet + + +class ObjectInfo( + ApiForGet, +): + pass diff --git a/comfy/api/apis/paths/prompt.py b/comfy/api/apis/paths/prompt.py new file mode 100644 index 000000000..f86428e17 --- /dev/null +++ b/comfy/api/apis/paths/prompt.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.prompt.get.operation import ApiForGet +from comfy.api.paths.prompt.post.operation import ApiForPost + + +class Prompt( + ApiForGet, + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/queue.py b/comfy/api/apis/paths/queue.py new file mode 100644 index 000000000..c40935878 --- /dev/null +++ b/comfy/api/apis/paths/queue.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.queue.get.operation import ApiForGet +from comfy.api.paths.queue.post.operation import ApiForPost + + +class Queue( + ApiForGet, + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/solidus.py b/comfy/api/apis/paths/solidus.py new file mode 100644 index 000000000..a3c32e56d --- /dev/null +++ b/comfy/api/apis/paths/solidus.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.solidus.get.operation import ApiForGet + + +class Solidus( + ApiForGet, +): + pass diff --git a/comfy/api/apis/paths/upload_image.py b/comfy/api/apis/paths/upload_image.py new file mode 100644 index 000000000..5bf9ec2a7 --- /dev/null +++ b/comfy/api/apis/paths/upload_image.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.upload_image.post.operation import ApiForPost + + +class UploadImage( + ApiForPost, +): + pass diff --git a/comfy/api/apis/paths/view.py b/comfy/api/apis/paths/view.py new file mode 100644 index 000000000..7a98680b5 --- /dev/null +++ b/comfy/api/apis/paths/view.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.view.get.operation import ApiForGet + + +class View( + ApiForGet, +): + pass diff --git a/comfy/api/apis/tag_to_api.py b/comfy/api/apis/tag_to_api.py new file mode 100644 index 000000000..447dbbb3f --- /dev/null +++ b/comfy/api/apis/tag_to_api.py @@ -0,0 +1,17 @@ +import typing +import typing_extensions + +from comfy.api.apis.tags.default_api import DefaultApi + +TagToApi = typing.TypedDict( + 'TagToApi', + { + "default": typing.Type[DefaultApi], + } +) + +tag_to_api = TagToApi( + { + "default": DefaultApi, + } +) diff --git a/comfy/api/apis/tags/__init__.py b/comfy/api/apis/tags/__init__.py new file mode 100644 index 000000000..652e31711 --- /dev/null +++ b/comfy/api/apis/tags/__init__.py @@ -0,0 +1,3 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.tag_to_api import tag_to_api \ No newline at end of file diff --git a/comfy/api/apis/tags/default_api.py b/comfy/api/apis/tags/default_api.py new file mode 100644 index 000000000..8c1bdf79d --- /dev/null +++ b/comfy/api/apis/tags/default_api.py @@ -0,0 +1,48 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.paths.api_v1_images_digest.get.operation import ApiV1ImagesDigestGet +from comfy.api.paths.api_v1_prompts.get.operation import ApiV1PromptsGet +from comfy.api.paths.api_v1_prompts.post.operation import ApiV1PromptsPost +from comfy.api.paths.prompt.get.operation import GetPrompt +from comfy.api.paths.prompt.post.operation import PostPrompt +from comfy.api.paths.extensions.get.operation import GetExtensions +from comfy.api.paths.interrupt.post.operation import PostInterrupt +from comfy.api.paths.history.get.operation import GetHistory +from comfy.api.paths.history.post.operation import PostHistory +from comfy.api.paths.queue.get.operation import GetQueue +from comfy.api.paths.queue.post.operation import PostQueue +from comfy.api.paths.upload_image.post.operation import UploadImage +from comfy.api.paths.object_info.get.operation import GetObjectInfo +from comfy.api.paths.view.get.operation import ViewImage +from comfy.api.paths.embeddings.get.operation import GetEmbeddings +from comfy.api.paths.solidus.get.operation import GetRoot + + +class DefaultApi( + ApiV1ImagesDigestGet, + ApiV1PromptsGet, + ApiV1PromptsPost, + GetPrompt, + PostPrompt, + GetExtensions, + PostInterrupt, + GetHistory, + PostHistory, + GetQueue, + PostQueue, + UploadImage, + GetObjectInfo, + ViewImage, + GetEmbeddings, + GetRoot, +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + pass diff --git a/comfy/api/components/__init__.py b/comfy/api/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/components/schema/__init__.py b/comfy/api/components/schema/__init__.py new file mode 100644 index 000000000..789602b45 --- /dev/null +++ b/comfy/api/components/schema/__init__.py @@ -0,0 +1,5 @@ +# we can not import model classes here because that would create a circular +# reference which would not work in python2 +# do not import all models into this module because that uses a lot of memory and stack frames +# if you need the ability to import all models from one package, import them with +# from comfy.api.components.schemas import ModelA, ModelB diff --git a/comfy/api/components/schema/extra_data.py b/comfy/api/components/schema/extra_data.py new file mode 100644 index 000000000..12a238678 --- /dev/null +++ b/comfy/api/components/schema/extra_data.py @@ -0,0 +1,205 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +from comfy.api.components.schema import workflow +Properties = typing.TypedDict( + 'Properties', + { + "workflow": typing.Type[workflow.Workflow], + } +) + + +class ExtraPnginfoDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "workflow", + }) + + def __new__( + cls, + *, + workflow: typing.Union[ + workflow.WorkflowDictInput, + workflow.WorkflowDict, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("workflow", workflow), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ExtraPnginfoDictInput, arg_) + return ExtraPnginfo.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ExtraPnginfoDictInput, + ExtraPnginfoDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExtraPnginfoDict: + return ExtraPnginfo.validate(arg, configuration=configuration) + + @property + def workflow(self) -> typing.Union[workflow.WorkflowDict, schemas.Unset]: + val = self.get("workflow", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + workflow.WorkflowDict, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ExtraPnginfoDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class ExtraPnginfo( + schemas.Schema[ExtraPnginfoDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ExtraPnginfoDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ExtraPnginfoDictInput, + ExtraPnginfoDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExtraPnginfoDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Properties2 = typing.TypedDict( + 'Properties2', + { + "extra_pnginfo": typing.Type[ExtraPnginfo], + } +) + + +class ExtraDataDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "extra_pnginfo", + }) + + def __new__( + cls, + *, + extra_pnginfo: typing.Union[ + ExtraPnginfoDictInput, + ExtraPnginfoDict, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("extra_pnginfo", extra_pnginfo), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ExtraDataDictInput, arg_) + return ExtraData.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ExtraDataDictInput, + ExtraDataDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExtraDataDict: + return ExtraData.validate(arg, configuration=configuration) + + @property + def extra_pnginfo(self) -> typing.Union[ExtraPnginfoDict, schemas.Unset]: + val = self.get("extra_pnginfo", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + ExtraPnginfoDict, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ExtraDataDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class ExtraData( + schemas.Schema[ExtraDataDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties2 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties2)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ExtraDataDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ExtraDataDictInput, + ExtraDataDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExtraDataDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schema/node.py b/comfy/api/components/schema/node.py new file mode 100644 index 000000000..49f86d111 --- /dev/null +++ b/comfy/api/components/schema/node.py @@ -0,0 +1,959 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +_0: typing_extensions.TypeAlias = schemas.StrSchema +_1: typing_extensions.TypeAlias = schemas.NumberSchema +Default: typing_extensions.TypeAlias = schemas.StrSchema +Min: typing_extensions.TypeAlias = schemas.NumberSchema +Max: typing_extensions.TypeAlias = schemas.NumberSchema +Step: typing_extensions.TypeAlias = schemas.NumberSchema +Multiline: typing_extensions.TypeAlias = schemas.BoolSchema +Properties = typing.TypedDict( + 'Properties', + { + "default": typing.Type[Default], + "min": typing.Type[Min], + "max": typing.Type[Max], + "step": typing.Type[Step], + "multiline": typing.Type[Multiline], + } +) + + +class _2Dict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "default", + "min", + "max", + "step", + "multiline", + }) + + def __new__( + cls, + *, + default: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + min: typing.Union[ + int, + float, + schemas.Unset + ] = schemas.unset, + max: typing.Union[ + int, + float, + schemas.Unset + ] = schemas.unset, + step: typing.Union[ + int, + float, + schemas.Unset + ] = schemas.unset, + multiline: typing.Union[ + bool, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("default", default), + ("min", min), + ("max", max), + ("step", step), + ("multiline", multiline), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(_2DictInput, arg_) + return _2.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + _2DictInput, + _2Dict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _2Dict: + return _2.validate(arg, configuration=configuration) + + @property + def default(self) -> typing.Union[str, schemas.Unset]: + val = self.get("default", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def min(self) -> typing.Union[int, float, schemas.Unset]: + val = self.get("min", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[int, float], + val + ) + + @property + def max(self) -> typing.Union[int, float, schemas.Unset]: + val = self.get("max", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[int, float], + val + ) + + @property + def step(self) -> typing.Union[int, float, schemas.Unset]: + val = self.get("step", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[int, float], + val + ) + + @property + def multiline(self) -> typing.Union[bool, schemas.Unset]: + val = self.get("multiline", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + bool, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +_2DictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class _2( + schemas.Schema[_2Dict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: _2Dict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + _2DictInput, + _2Dict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _2Dict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Items2: typing_extensions.TypeAlias = schemas.StrSchema + + +class _3Tuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[_3TupleInput, _3Tuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return _3.validate(arg, configuration=configuration) +_3TupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class _3( + schemas.Schema[schemas.immutabledict, _3Tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items2] = dataclasses.field(default_factory=lambda: Items2) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: _3Tuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + _3TupleInput, + _3Tuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _3Tuple: + return super().validate_base( + arg, + configuration=configuration, + ) +OneOf = typing.Tuple[ + typing.Type[_0], + typing.Type[_1], + typing.Type[_2], + typing.Type[_3], +] + + +@dataclasses.dataclass(frozen=True) +class Items( + schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]], +): + # any type + max_items: int = 2 + min_items: int = 1 + one_of: OneOf = dataclasses.field(default_factory=lambda: schemas.tuple_to_instance(OneOf)) # type: ignore + + + +class AdditionalPropertiesTuple( + typing.Tuple[ + schemas.OUTPUT_BASE_TYPES, + ... + ] +): + + def __new__(cls, arg: typing.Union[AdditionalPropertiesTupleInput, AdditionalPropertiesTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return AdditionalProperties.validate(arg, configuration=configuration) +AdditionalPropertiesTupleInput = typing.Union[ + typing.List[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ], + typing.Tuple[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class AdditionalProperties( + schemas.Schema[schemas.immutabledict, AdditionalPropertiesTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: AdditionalPropertiesTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + AdditionalPropertiesTupleInput, + AdditionalPropertiesTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> AdditionalPropertiesTuple: + return super().validate_base( + arg, + configuration=configuration, + ) + + +class RequiredDict(schemas.immutabledict[str, AdditionalPropertiesTuple]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + AdditionalPropertiesTupleInput, + AdditionalPropertiesTuple + ], + ): + used_kwargs = typing.cast(RequiredDictInput, kwargs) + return Required.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + RequiredDictInput, + RequiredDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> RequiredDict: + return Required.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[AdditionalPropertiesTuple, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + AdditionalPropertiesTuple, + val + ) +RequiredDictInput = typing.Mapping[ + str, + typing.Union[ + AdditionalPropertiesTupleInput, + AdditionalPropertiesTuple + ], +] + + +@dataclasses.dataclass(frozen=True) +class Required( + schemas.Schema[RequiredDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: RequiredDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + RequiredDictInput, + RequiredDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> RequiredDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +AdditionalProperties2: typing_extensions.TypeAlias = schemas.StrSchema + + +class HiddenDict(schemas.immutabledict[str, str]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: str, + ): + used_kwargs = typing.cast(HiddenDictInput, kwargs) + return Hidden.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + HiddenDictInput, + HiddenDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> HiddenDict: + return Hidden.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[str, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) +HiddenDictInput = typing.Mapping[ + str, + str, +] + + +@dataclasses.dataclass(frozen=True) +class Hidden( + schemas.Schema[HiddenDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[AdditionalProperties2] = dataclasses.field(default_factory=lambda: AdditionalProperties2) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: HiddenDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + HiddenDictInput, + HiddenDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> HiddenDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Properties2 = typing.TypedDict( + 'Properties2', + { + "required": typing.Type[Required], + "hidden": typing.Type[Hidden], + } +) + + +class InputDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + "required", + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "hidden", + }) + + def __new__( + cls, + *, + required: typing.Union[ + RequiredDictInput, + RequiredDict, + ], + hidden: typing.Union[ + HiddenDictInput, + HiddenDict, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = { + "required": required, + } + for key_, val in ( + ("hidden", hidden), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(InputDictInput, arg_) + return Input.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + InputDictInput, + InputDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> InputDict: + return Input.validate(arg, configuration=configuration) + + @property + def required(self) -> RequiredDict: + return typing.cast( + RequiredDict, + self.__getitem__("required") + ) + + @property + def hidden(self) -> typing.Union[HiddenDict, schemas.Unset]: + val = self.get("hidden", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + HiddenDict, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +InputDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Input( + schemas.Schema[InputDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + required: typing.FrozenSet[str] = frozenset({ + "required", + }) + properties: Properties2 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties2)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: InputDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + InputDictInput, + InputDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> InputDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Items3: typing_extensions.TypeAlias = schemas.StrSchema + + +class OutputTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[OutputTupleInput, OutputTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Output.validate(arg, configuration=configuration) +OutputTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Output( + schemas.Schema[schemas.immutabledict, OutputTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items3] = dataclasses.field(default_factory=lambda: Items3) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: OutputTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + OutputTupleInput, + OutputTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> OutputTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Items4: typing_extensions.TypeAlias = schemas.BoolSchema + + +class OutputIsListTuple( + typing.Tuple[ + bool, + ... + ] +): + + def __new__(cls, arg: typing.Union[OutputIsListTupleInput, OutputIsListTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return OutputIsList.validate(arg, configuration=configuration) +OutputIsListTupleInput = typing.Union[ + typing.List[ + bool, + ], + typing.Tuple[ + bool, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class OutputIsList( + schemas.Schema[schemas.immutabledict, OutputIsListTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items4] = dataclasses.field(default_factory=lambda: Items4) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: OutputIsListTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + OutputIsListTupleInput, + OutputIsListTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> OutputIsListTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Items5: typing_extensions.TypeAlias = schemas.StrSchema + + +class OutputNameTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[OutputNameTupleInput, OutputNameTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return OutputName.validate(arg, configuration=configuration) +OutputNameTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class OutputName( + schemas.Schema[schemas.immutabledict, OutputNameTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items5] = dataclasses.field(default_factory=lambda: Items5) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: OutputNameTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + OutputNameTupleInput, + OutputNameTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> OutputNameTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Name: typing_extensions.TypeAlias = schemas.StrSchema +DisplayName: typing_extensions.TypeAlias = schemas.StrSchema +Description: typing_extensions.TypeAlias = schemas.StrSchema +Category: typing_extensions.TypeAlias = schemas.StrSchema +OutputNode: typing_extensions.TypeAlias = schemas.BoolSchema +Properties3 = typing.TypedDict( + 'Properties3', + { + "input": typing.Type[Input], + "output": typing.Type[Output], + "output_is_list": typing.Type[OutputIsList], + "output_name": typing.Type[OutputName], + "name": typing.Type[Name], + "display_name": typing.Type[DisplayName], + "description": typing.Type[Description], + "category": typing.Type[Category], + "output_node": typing.Type[OutputNode], + } +) + + +class NodeDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "input", + "output", + "output_is_list", + "output_name", + "name", + "display_name", + "description", + "category", + "output_node", + }) + + def __new__( + cls, + *, + input: typing.Union[ + InputDictInput, + InputDict, + schemas.Unset + ] = schemas.unset, + output: typing.Union[ + OutputTupleInput, + OutputTuple, + schemas.Unset + ] = schemas.unset, + output_is_list: typing.Union[ + OutputIsListTupleInput, + OutputIsListTuple, + schemas.Unset + ] = schemas.unset, + output_name: typing.Union[ + OutputNameTupleInput, + OutputNameTuple, + schemas.Unset + ] = schemas.unset, + name: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + display_name: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + description: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + category: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + output_node: typing.Union[ + bool, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("input", input), + ("output", output), + ("output_is_list", output_is_list), + ("output_name", output_name), + ("name", name), + ("display_name", display_name), + ("description", description), + ("category", category), + ("output_node", output_node), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(NodeDictInput, arg_) + return Node.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + NodeDictInput, + NodeDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> NodeDict: + return Node.validate(arg, configuration=configuration) + + @property + def input(self) -> typing.Union[InputDict, schemas.Unset]: + val = self.get("input", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + InputDict, + val + ) + + @property + def output(self) -> typing.Union[OutputTuple, schemas.Unset]: + val = self.get("output", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + OutputTuple, + val + ) + + @property + def output_is_list(self) -> typing.Union[OutputIsListTuple, schemas.Unset]: + val = self.get("output_is_list", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + OutputIsListTuple, + val + ) + + @property + def output_name(self) -> typing.Union[OutputNameTuple, schemas.Unset]: + val = self.get("output_name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + OutputNameTuple, + val + ) + + @property + def name(self) -> typing.Union[str, schemas.Unset]: + val = self.get("name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def display_name(self) -> typing.Union[str, schemas.Unset]: + val = self.get("display_name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def description(self) -> typing.Union[str, schemas.Unset]: + val = self.get("description", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def category(self) -> typing.Union[str, schemas.Unset]: + val = self.get("category", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def output_node(self) -> typing.Union[bool, schemas.Unset]: + val = self.get("output_node", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + bool, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +NodeDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Node( + schemas.Schema[NodeDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties3 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties3)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: NodeDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + NodeDictInput, + NodeDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> NodeDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schema/prompt.py b/comfy/api/components/schema/prompt.py new file mode 100644 index 000000000..72974f7da --- /dev/null +++ b/comfy/api/components/schema/prompt.py @@ -0,0 +1,100 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +from comfy.api.components.schema import prompt_node + + +class PromptDict(schemas.immutabledict[str, prompt_node.PromptNodeDict]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + prompt_node.PromptNodeDictInput, + prompt_node.PromptNodeDict, + ], + ): + used_kwargs = typing.cast(PromptDictInput, kwargs) + return Prompt.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + PromptDictInput, + PromptDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptDict: + return Prompt.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[prompt_node.PromptNodeDict, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + prompt_node.PromptNodeDict, + val + ) +PromptDictInput = typing.Mapping[ + str, + typing.Union[ + prompt_node.PromptNodeDictInput, + prompt_node.PromptNodeDict, + ], +] + + +@dataclasses.dataclass(frozen=True) +class Prompt( + schemas.Schema[PromptDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + + The keys are stringified integers corresponding to nodes. + +You can retrieve the last prompt run using GET /api/v1/prompts + + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[prompt_node.PromptNode] = dataclasses.field(default_factory=lambda: prompt_node.PromptNode) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: PromptDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + PromptDictInput, + PromptDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schema/prompt_node.py b/comfy/api/components/schema/prompt_node.py new file mode 100644 index 000000000..291c37c42 --- /dev/null +++ b/comfy/api/components/schema/prompt_node.py @@ -0,0 +1,405 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +ClassType: typing_extensions.TypeAlias = schemas.StrSchema +_0: typing_extensions.TypeAlias = schemas.NumberSchema +_1: typing_extensions.TypeAlias = schemas.StrSchema +_2: typing_extensions.TypeAlias = schemas.BoolSchema + + +class ItemsTuple( + typing.Tuple[ + str, + int, + typing_extensions.Unpack[typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]] + ] +): + + def __new__(cls, arg: typing.Union[ItemsTupleInput, ItemsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Items.validate(arg, configuration=configuration) +ItemsTupleInput = typing.Union[ + typing.List[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ], + typing.Tuple[ + str, + int, + typing_extensions.Unpack[typing.Tuple[schemas.INPUT_TYPES_ALL, ...]] + ] +] +_02: typing_extensions.TypeAlias = schemas.StrSchema +_12: typing_extensions.TypeAlias = schemas.IntSchema + + +@dataclasses.dataclass(frozen=True) +class Items( + schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], ItemsTuple], +): + # any type + prefix_items: typing.Tuple[ + typing.Type[_02], + typing.Type[_12], + ] = ( + _02, + _12, + ) + max_items: int = 2 + min_items: int = 2 + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: ItemsTuple, + } + ) + + + +class _3Tuple( + typing.Tuple[ + schemas.OUTPUT_BASE_TYPES, + ... + ] +): + + def __new__(cls, arg: typing.Union[_3TupleInput, _3Tuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return _3.validate(arg, configuration=configuration) +_3TupleInput = typing.Union[ + typing.List[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ], + typing.Tuple[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class _3( + schemas.Schema[schemas.immutabledict, _3Tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: _3Tuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + _3TupleInput, + _3Tuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _3Tuple: + return super().validate_base( + arg, + configuration=configuration, + ) +OneOf = typing.Tuple[ + typing.Type[_0], + typing.Type[_1], + typing.Type[_2], + typing.Type[_3], +] + + +@dataclasses.dataclass(frozen=True) +class AdditionalProperties( + schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]], +): + # any type + one_of: OneOf = dataclasses.field(default_factory=lambda: schemas.tuple_to_instance(OneOf)) # type: ignore + + + +class InputsDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ): + used_kwargs = typing.cast(InputsDictInput, kwargs) + return Inputs.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + InputsDictInput, + InputsDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> InputsDict: + return Inputs.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.OUTPUT_BASE_TYPES, + val + ) +InputsDictInput = typing.Mapping[ + str, + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], +] + + +@dataclasses.dataclass(frozen=True) +class Inputs( + schemas.Schema[InputsDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: InputsDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + InputsDictInput, + InputsDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> InputsDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Items2: typing_extensions.TypeAlias = schemas.StrSchema + + +class _0Tuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[_0TupleInput, _0Tuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return _03.validate(arg, configuration=configuration) +_0TupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class _03( + schemas.Schema[schemas.immutabledict, _0Tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items2] = dataclasses.field(default_factory=lambda: Items2) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: _0Tuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + _0TupleInput, + _0Tuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _0Tuple: + return super().validate_base( + arg, + configuration=configuration, + ) +_13: typing_extensions.TypeAlias = schemas.StrSchema +OneOf2 = typing.Tuple[ + typing.Type[_03], + typing.Type[_13], +] + + +@dataclasses.dataclass(frozen=True) +class IsChanged( + schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]], +): + # any type + one_of: OneOf2 = dataclasses.field(default_factory=lambda: schemas.tuple_to_instance(OneOf2)) # type: ignore + +Properties = typing.TypedDict( + 'Properties', + { + "class_type": typing.Type[ClassType], + "inputs": typing.Type[Inputs], + "is_changed": typing.Type[IsChanged], + } +) + + +class PromptNodeDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + "class_type", + "inputs", + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "is_changed", + }) + + def __new__( + cls, + *, + class_type: str, + inputs: typing.Union[ + InputsDictInput, + InputsDict, + ], + is_changed: typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = { + "class_type": class_type, + "inputs": inputs, + } + for key_, val in ( + ("is_changed", is_changed), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(PromptNodeDictInput, arg_) + return PromptNode.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + PromptNodeDictInput, + PromptNodeDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptNodeDict: + return PromptNode.validate(arg, configuration=configuration) + + @property + def class_type(self) -> str: + return typing.cast( + str, + self.__getitem__("class_type") + ) + + @property + def inputs(self) -> InputsDict: + return typing.cast( + InputsDict, + self.__getitem__("inputs") + ) + + @property + def is_changed(self) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + val = self.get("is_changed", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return val + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +PromptNodeDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class PromptNode( + schemas.Schema[PromptNodeDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + required: typing.FrozenSet[str] = frozenset({ + "class_type", + "inputs", + }) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: PromptNodeDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + PromptNodeDictInput, + PromptNodeDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptNodeDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schema/prompt_request.py b/comfy/api/components/schema/prompt_request.py new file mode 100644 index 000000000..c9549ba85 --- /dev/null +++ b/comfy/api/components/schema/prompt_request.py @@ -0,0 +1,149 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +ClientId: typing_extensions.TypeAlias = schemas.StrSchema + +from comfy.api.components.schema import extra_data +from comfy.api.components.schema import prompt +Properties = typing.TypedDict( + 'Properties', + { + "client_id": typing.Type[ClientId], + "prompt": typing.Type[prompt.Prompt], + "extra_data": typing.Type[extra_data.ExtraData], + } +) + + +class PromptRequestDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + "prompt", + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "client_id", + "extra_data", + }) + + def __new__( + cls, + *, + prompt: typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + ], + client_id: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + extra_data: typing.Union[ + extra_data.ExtraDataDictInput, + extra_data.ExtraDataDict, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = { + "prompt": prompt, + } + for key_, val in ( + ("client_id", client_id), + ("extra_data", extra_data), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(PromptRequestDictInput, arg_) + return PromptRequest.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + PromptRequestDictInput, + PromptRequestDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptRequestDict: + return PromptRequest.validate(arg, configuration=configuration) + + @property + def prompt(self) -> prompt.PromptDict: + return typing.cast( + prompt.PromptDict, + self.__getitem__("prompt") + ) + + @property + def client_id(self) -> typing.Union[str, schemas.Unset]: + val = self.get("client_id", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def extra_data(self) -> typing.Union[extra_data.ExtraDataDict, schemas.Unset]: + val = self.get("extra_data", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + extra_data.ExtraDataDict, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +PromptRequestDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class PromptRequest( + schemas.Schema[PromptRequestDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + required: typing.FrozenSet[str] = frozenset({ + "prompt", + }) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: PromptRequestDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + PromptRequestDictInput, + PromptRequestDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PromptRequestDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schema/queue_tuple.py b/comfy/api/components/schema/queue_tuple.py new file mode 100644 index 000000000..fe8992ae8 --- /dev/null +++ b/comfy/api/components/schema/queue_tuple.py @@ -0,0 +1,166 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations + +from comfy.api.components.schema import prompt, extra_data +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + + +class QueueTupleTuple( + typing.Tuple[ + typing.Union[int, float], + str, + prompt.PromptDict, + extra_data.ExtraDataDict, + "_4Tuple", + typing_extensions.Unpack[typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]] + ] +): + + def __new__(cls, arg: typing.Union[QueueTupleTupleInput, QueueTupleTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return QueueTuple.validate(arg, configuration=configuration) +QueueTupleTupleInput = typing.Union[ + typing.List[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ], + typing.Tuple[ + typing.Union[ + int, + float + ], + str, + typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + ], + typing.Union[ + extra_data.ExtraDataDictInput, + extra_data.ExtraDataDict, + ], + typing.Union[ + "_4TupleInput", + "_4Tuple" + ], + typing_extensions.Unpack[typing.Tuple[schemas.INPUT_TYPES_ALL, ...]] + ] +] +_0: typing_extensions.TypeAlias = schemas.NumberSchema +_1: typing_extensions.TypeAlias = schemas.StrSchema +Items: typing_extensions.TypeAlias = schemas.StrSchema + + +class _4Tuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[_4TupleInput, _4Tuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return _4.validate(arg, configuration=configuration) +_4TupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class _4( + schemas.Schema[schemas.immutabledict, _4Tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: _4Tuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + _4TupleInput, + _4Tuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> _4Tuple: + return super().validate_base( + arg, + configuration=configuration, + ) + + +@dataclasses.dataclass(frozen=True) +class QueueTuple( + schemas.Schema[schemas.immutabledict, QueueTupleTuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + + The first item is the queue priority +The second item is the hash id of the prompt object +The third item is a Prompt +The fourth item is optionally an ExtraData +The fifth item is optionally a list of "Good Outputs" node IDs. + + """ + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + max_items: int = 5 + min_items: int = 3 + prefix_items: typing.Tuple[ + typing.Type[_0], + typing.Type[_1], + typing.Type[prompt.Prompt], + typing.Type[extra_data.ExtraData], + typing.Type[_4], + ] = ( + _0, + _1, + prompt.Prompt, + extra_data.ExtraData, + _4, + ) + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: QueueTupleTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + QueueTupleTupleInput, + QueueTupleTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> QueueTupleTuple: + return super().validate_base( + arg, + configuration=configuration, + ) diff --git a/comfy/api/components/schema/workflow.py b/comfy/api/components/schema/workflow.py new file mode 100644 index 000000000..360ede7f4 --- /dev/null +++ b/comfy/api/components/schema/workflow.py @@ -0,0 +1,1457 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +LastNodeId: typing_extensions.TypeAlias = schemas.IntSchema +LastLinkId: typing_extensions.TypeAlias = schemas.IntSchema +Id: typing_extensions.TypeAlias = schemas.IntSchema +Type: typing_extensions.TypeAlias = schemas.StrSchema +Items2: typing_extensions.TypeAlias = schemas.NumberSchema + + +class PosTuple( + typing.Tuple[ + typing.Union[int, float], + ... + ] +): + + def __new__(cls, arg: typing.Union[PosTupleInput, PosTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Pos.validate(arg, configuration=configuration) +PosTupleInput = typing.Union[ + typing.List[ + typing.Union[ + int, + float + ], + ], + typing.Tuple[ + typing.Union[ + int, + float + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Pos( + schemas.Schema[schemas.immutabledict, PosTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + max_items: int = 2 + min_items: int = 2 + items: typing.Type[Items2] = dataclasses.field(default_factory=lambda: Items2) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: PosTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + PosTupleInput, + PosTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PosTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +_0: typing_extensions.TypeAlias = schemas.NumberSchema +_1: typing_extensions.TypeAlias = schemas.NumberSchema +Properties = typing.TypedDict( + 'Properties', + { + "0": typing.Type[_0], + "1": typing.Type[_1], + } +) + + +class SizeDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "0", + "1", + }) + + def __new__( + cls, + *, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + arg_.update(kwargs) + used_arg_ = typing.cast(SizeDictInput, arg_) + return Size.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SizeDictInput, + SizeDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SizeDict: + return Size.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SizeDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Size( + schemas.Schema[SizeDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SizeDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SizeDictInput, + SizeDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SizeDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +AdditionalProperties: typing_extensions.TypeAlias = schemas.DictSchema + + +class FlagsDict(schemas.immutabledict[str, schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + ], + ): + used_kwargs = typing.cast(FlagsDictInput, kwargs) + return Flags.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + FlagsDictInput, + FlagsDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> FlagsDict: + return Flags.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + val + ) +FlagsDictInput = typing.Mapping[ + str, + typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + ], +] + + +@dataclasses.dataclass(frozen=True) +class Flags( + schemas.Schema[FlagsDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: FlagsDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + FlagsDictInput, + FlagsDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> FlagsDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Order: typing_extensions.TypeAlias = schemas.IntSchema +Mode: typing_extensions.TypeAlias = schemas.IntSchema +Name: typing_extensions.TypeAlias = schemas.StrSchema +Type2: typing_extensions.TypeAlias = schemas.StrSchema +Link: typing_extensions.TypeAlias = schemas.IntSchema +Properties2 = typing.TypedDict( + 'Properties2', + { + "name": typing.Type[Name], + "type": typing.Type[Type2], + "link": typing.Type[Link], + } +) + + +class ItemsDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "name", + "type", + "link", + }) + + def __new__( + cls, + *, + name: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + type: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + link: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("name", name), + ("type", type), + ("link", link), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ItemsDictInput, arg_) + return Items3.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ItemsDictInput, + ItemsDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict: + return Items3.validate(arg, configuration=configuration) + + @property + def name(self) -> typing.Union[str, schemas.Unset]: + val = self.get("name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def type(self) -> typing.Union[str, schemas.Unset]: + val = self.get("type", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def link(self) -> typing.Union[int, schemas.Unset]: + val = self.get("link", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ItemsDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Items3( + schemas.Schema[ItemsDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties2 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties2)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ItemsDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ItemsDictInput, + ItemsDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict: + return super().validate_base( + arg, + configuration=configuration, + ) + + + +class InputsTuple( + typing.Tuple[ + ItemsDict, + ... + ] +): + + def __new__(cls, arg: typing.Union[InputsTupleInput, InputsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Inputs.validate(arg, configuration=configuration) +InputsTupleInput = typing.Union[ + typing.List[ + typing.Union[ + ItemsDictInput, + ItemsDict, + ], + ], + typing.Tuple[ + typing.Union[ + ItemsDictInput, + ItemsDict, + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Inputs( + schemas.Schema[schemas.immutabledict, InputsTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items3] = dataclasses.field(default_factory=lambda: Items3) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: InputsTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + InputsTupleInput, + InputsTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> InputsTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Name2: typing_extensions.TypeAlias = schemas.StrSchema +Type3: typing_extensions.TypeAlias = schemas.StrSchema +Items5: typing_extensions.TypeAlias = schemas.IntSchema + + +class LinksTuple( + typing.Tuple[ + int, + ... + ] +): + + def __new__(cls, arg: typing.Union[LinksTupleInput, LinksTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Links.validate(arg, configuration=configuration) +LinksTupleInput = typing.Union[ + typing.List[ + int, + ], + typing.Tuple[ + int, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Links( + schemas.Schema[schemas.immutabledict, LinksTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items5] = dataclasses.field(default_factory=lambda: Items5) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: LinksTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + LinksTupleInput, + LinksTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> LinksTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +SlotIndex: typing_extensions.TypeAlias = schemas.IntSchema +Properties3 = typing.TypedDict( + 'Properties3', + { + "name": typing.Type[Name2], + "type": typing.Type[Type3], + "links": typing.Type[Links], + "slot_index": typing.Type[SlotIndex], + } +) + + +class ItemsDict2(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "name", + "type", + "links", + "slot_index", + }) + + def __new__( + cls, + *, + name: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + type: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + links: typing.Union[ + LinksTupleInput, + LinksTuple, + schemas.Unset + ] = schemas.unset, + slot_index: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("name", name), + ("type", type), + ("links", links), + ("slot_index", slot_index), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ItemsDictInput2, arg_) + return Items4.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ItemsDictInput2, + ItemsDict2 + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict2: + return Items4.validate(arg, configuration=configuration) + + @property + def name(self) -> typing.Union[str, schemas.Unset]: + val = self.get("name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def type(self) -> typing.Union[str, schemas.Unset]: + val = self.get("type", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def links(self) -> typing.Union[LinksTuple, schemas.Unset]: + val = self.get("links", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + LinksTuple, + val + ) + + @property + def slot_index(self) -> typing.Union[int, schemas.Unset]: + val = self.get("slot_index", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ItemsDictInput2 = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Items4( + schemas.Schema[ItemsDict2, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties3 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties3)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ItemsDict2 + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ItemsDictInput2, + ItemsDict2, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict2: + return super().validate_base( + arg, + configuration=configuration, + ) + + + +class OutputsTuple( + typing.Tuple[ + ItemsDict2, + ... + ] +): + + def __new__(cls, arg: typing.Union[OutputsTupleInput, OutputsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Outputs.validate(arg, configuration=configuration) +OutputsTupleInput = typing.Union[ + typing.List[ + typing.Union[ + ItemsDictInput2, + ItemsDict2, + ], + ], + typing.Tuple[ + typing.Union[ + ItemsDictInput2, + ItemsDict2, + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Outputs( + schemas.Schema[schemas.immutabledict, OutputsTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items4] = dataclasses.field(default_factory=lambda: Items4) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: OutputsTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + OutputsTupleInput, + OutputsTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> OutputsTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties4: typing_extensions.TypeAlias = schemas.DictSchema +Items6: typing_extensions.TypeAlias = schemas.StrSchema + + +class WidgetsValuesTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[WidgetsValuesTupleInput, WidgetsValuesTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return WidgetsValues.validate(arg, configuration=configuration) +WidgetsValuesTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class WidgetsValues( + schemas.Schema[schemas.immutabledict, WidgetsValuesTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items6] = dataclasses.field(default_factory=lambda: Items6) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: WidgetsValuesTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + WidgetsValuesTupleInput, + WidgetsValuesTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> WidgetsValuesTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties5 = typing.TypedDict( + 'Properties5', + { + "id": typing.Type[Id], + "type": typing.Type[Type], + "pos": typing.Type[Pos], + "size": typing.Type[Size], + "flags": typing.Type[Flags], + "order": typing.Type[Order], + "mode": typing.Type[Mode], + "inputs": typing.Type[Inputs], + "outputs": typing.Type[Outputs], + "properties": typing.Type[Properties4], + "widgets_values": typing.Type[WidgetsValues], + } +) + + +class ItemsDict3(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "id", + "type", + "pos", + "size", + "flags", + "order", + "mode", + "inputs", + "outputs", + "properties", + "widgets_values", + }) + + def __new__( + cls, + *, + id: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + type: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + pos: typing.Union[ + PosTupleInput, + PosTuple, + schemas.Unset + ] = schemas.unset, + size: typing.Union[ + SizeDictInput, + SizeDict, + schemas.Unset + ] = schemas.unset, + flags: typing.Union[ + FlagsDictInput, + FlagsDict, + schemas.Unset + ] = schemas.unset, + order: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + mode: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + inputs: typing.Union[ + InputsTupleInput, + InputsTuple, + schemas.Unset + ] = schemas.unset, + outputs: typing.Union[ + OutputsTupleInput, + OutputsTuple, + schemas.Unset + ] = schemas.unset, + properties: typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + schemas.Unset + ] = schemas.unset, + widgets_values: typing.Union[ + WidgetsValuesTupleInput, + WidgetsValuesTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("id", id), + ("type", type), + ("pos", pos), + ("size", size), + ("flags", flags), + ("order", order), + ("mode", mode), + ("inputs", inputs), + ("outputs", outputs), + ("properties", properties), + ("widgets_values", widgets_values), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ItemsDictInput3, arg_) + return Items.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ItemsDictInput3, + ItemsDict3 + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict3: + return Items.validate(arg, configuration=configuration) + + @property + def id(self) -> typing.Union[int, schemas.Unset]: + val = self.get("id", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + @property + def type(self) -> typing.Union[str, schemas.Unset]: + val = self.get("type", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def pos(self) -> typing.Union[PosTuple, schemas.Unset]: + val = self.get("pos", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + PosTuple, + val + ) + + @property + def size(self) -> typing.Union[SizeDict, schemas.Unset]: + val = self.get("size", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + SizeDict, + val + ) + + @property + def flags(self) -> typing.Union[FlagsDict, schemas.Unset]: + val = self.get("flags", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + FlagsDict, + val + ) + + @property + def order(self) -> typing.Union[int, schemas.Unset]: + val = self.get("order", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + @property + def mode(self) -> typing.Union[int, schemas.Unset]: + val = self.get("mode", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + @property + def inputs(self) -> typing.Union[InputsTuple, schemas.Unset]: + val = self.get("inputs", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + InputsTuple, + val + ) + + @property + def outputs(self) -> typing.Union[OutputsTuple, schemas.Unset]: + val = self.get("outputs", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + OutputsTuple, + val + ) + + @property + def properties(self) -> typing.Union[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], schemas.Unset]: + val = self.get("properties", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + val + ) + + @property + def widgets_values(self) -> typing.Union[WidgetsValuesTuple, schemas.Unset]: + val = self.get("widgets_values", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + WidgetsValuesTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ItemsDictInput3 = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Items( + schemas.Schema[ItemsDict3, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties5 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties5)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ItemsDict3 + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ItemsDictInput3, + ItemsDict3, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsDict3: + return super().validate_base( + arg, + configuration=configuration, + ) + + + +class NodesTuple( + typing.Tuple[ + ItemsDict3, + ... + ] +): + + def __new__(cls, arg: typing.Union[NodesTupleInput, NodesTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Nodes.validate(arg, configuration=configuration) +NodesTupleInput = typing.Union[ + typing.List[ + typing.Union[ + ItemsDictInput3, + ItemsDict3, + ], + ], + typing.Tuple[ + typing.Union[ + ItemsDictInput3, + ItemsDict3, + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Nodes( + schemas.Schema[schemas.immutabledict, NodesTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: NodesTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + NodesTupleInput, + NodesTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> NodesTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +_02: typing_extensions.TypeAlias = schemas.IntSchema +_12: typing_extensions.TypeAlias = schemas.StrSchema +OneOf = typing.Tuple[ + typing.Type[_02], + typing.Type[_12], +] + + +@dataclasses.dataclass(frozen=True) +class Items8( + schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]], +): + # any type + max_items: int = 6 + min_items: int = 6 + one_of: OneOf = dataclasses.field(default_factory=lambda: schemas.tuple_to_instance(OneOf)) # type: ignore + + + +class ItemsTuple( + typing.Tuple[ + schemas.OUTPUT_BASE_TYPES, + ... + ] +): + + def __new__(cls, arg: typing.Union[ItemsTupleInput, ItemsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Items7.validate(arg, configuration=configuration) +ItemsTupleInput = typing.Union[ + typing.List[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ], + typing.Tuple[ + typing.Union[ + schemas.INPUT_TYPES_ALL, + schemas.OUTPUT_BASE_TYPES + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Items7( + schemas.Schema[schemas.immutabledict, ItemsTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items8] = dataclasses.field(default_factory=lambda: Items8) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: ItemsTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ItemsTupleInput, + ItemsTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ItemsTuple: + return super().validate_base( + arg, + configuration=configuration, + ) + + +class LinksTuple2( + typing.Tuple[ + ItemsTuple, + ... + ] +): + + def __new__(cls, arg: typing.Union[LinksTupleInput2, LinksTuple2], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Links2.validate(arg, configuration=configuration) +LinksTupleInput2 = typing.Union[ + typing.List[ + typing.Union[ + ItemsTupleInput, + ItemsTuple + ], + ], + typing.Tuple[ + typing.Union[ + ItemsTupleInput, + ItemsTuple + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Links2( + schemas.Schema[schemas.immutabledict, LinksTuple2] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items7] = dataclasses.field(default_factory=lambda: Items7) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: LinksTuple2 + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + LinksTupleInput2, + LinksTuple2, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> LinksTuple2: + return super().validate_base( + arg, + configuration=configuration, + ) +Items9: typing_extensions.TypeAlias = schemas.DictSchema + + +class GroupsTuple( + typing.Tuple[ + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + ... + ] +): + + def __new__(cls, arg: typing.Union[GroupsTupleInput, GroupsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Groups.validate(arg, configuration=configuration) +GroupsTupleInput = typing.Union[ + typing.List[ + typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + ], + ], + typing.Tuple[ + typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Groups( + schemas.Schema[schemas.immutabledict, GroupsTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items9] = dataclasses.field(default_factory=lambda: Items9) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: GroupsTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + GroupsTupleInput, + GroupsTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> GroupsTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Config: typing_extensions.TypeAlias = schemas.DictSchema +Extra: typing_extensions.TypeAlias = schemas.DictSchema +Version: typing_extensions.TypeAlias = schemas.NumberSchema +Properties6 = typing.TypedDict( + 'Properties6', + { + "last_node_id": typing.Type[LastNodeId], + "last_link_id": typing.Type[LastLinkId], + "nodes": typing.Type[Nodes], + "links": typing.Type[Links2], + "groups": typing.Type[Groups], + "config": typing.Type[Config], + "extra": typing.Type[Extra], + "version": typing.Type[Version], + } +) + + +class WorkflowDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "last_node_id", + "last_link_id", + "nodes", + "links", + "groups", + "config", + "extra", + "version", + }) + + def __new__( + cls, + *, + last_node_id: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + last_link_id: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + nodes: typing.Union[ + NodesTupleInput, + NodesTuple, + schemas.Unset + ] = schemas.unset, + links: typing.Union[ + LinksTupleInput2, + LinksTuple2, + schemas.Unset + ] = schemas.unset, + groups: typing.Union[ + GroupsTupleInput, + GroupsTuple, + schemas.Unset + ] = schemas.unset, + config: typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + schemas.Unset + ] = schemas.unset, + extra: typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + schemas.Unset + ] = schemas.unset, + version: typing.Union[ + int, + float, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("last_node_id", last_node_id), + ("last_link_id", last_link_id), + ("nodes", nodes), + ("links", links), + ("groups", groups), + ("config", config), + ("extra", extra), + ("version", version), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(WorkflowDictInput, arg_) + return Workflow.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + WorkflowDictInput, + WorkflowDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> WorkflowDict: + return Workflow.validate(arg, configuration=configuration) + + @property + def last_node_id(self) -> typing.Union[int, schemas.Unset]: + val = self.get("last_node_id", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + @property + def last_link_id(self) -> typing.Union[int, schemas.Unset]: + val = self.get("last_link_id", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + @property + def nodes(self) -> typing.Union[NodesTuple, schemas.Unset]: + val = self.get("nodes", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + NodesTuple, + val + ) + + @property + def links(self) -> typing.Union[LinksTuple2, schemas.Unset]: + val = self.get("links", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + LinksTuple2, + val + ) + + @property + def groups(self) -> typing.Union[GroupsTuple, schemas.Unset]: + val = self.get("groups", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + GroupsTuple, + val + ) + + @property + def config(self) -> typing.Union[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], schemas.Unset]: + val = self.get("config", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + val + ) + + @property + def extra(self) -> typing.Union[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], schemas.Unset]: + val = self.get("extra", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + val + ) + + @property + def version(self) -> typing.Union[int, float, schemas.Unset]: + val = self.get("version", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[int, float], + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +WorkflowDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Workflow( + schemas.Schema[WorkflowDict, tuple] +): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator. + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + + Do not edit the class manually. + """ + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties6 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties6)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: WorkflowDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + WorkflowDictInput, + WorkflowDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> WorkflowDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/components/schemas/__init__.py b/comfy/api/components/schemas/__init__.py new file mode 100644 index 000000000..3fc956bf4 --- /dev/null +++ b/comfy/api/components/schemas/__init__.py @@ -0,0 +1,20 @@ +# coding: utf-8 + +# flake8: noqa + +# import all models into this package +# if you have many models here with many references from one model to another this may +# raise a RecursionError +# to avoid this, import only the models that you directly need like: +# from from comfy.api.components.schema.pet import Pet +# or import this package, but before doing it, use: +# import sys +# sys.setrecursionlimit(n) + +from comfy.api.components.schema.extra_data import ExtraData +from comfy.api.components.schema.node import Node +from comfy.api.components.schema.prompt import Prompt +from comfy.api.components.schema.prompt_node import PromptNode +from comfy.api.components.schema.prompt_request import PromptRequest +from comfy.api.components.schema.queue_tuple import QueueTuple +from comfy.api.components.schema.workflow import Workflow diff --git a/comfy/api/configuration.py b/comfy/api/configuration.py deleted file mode 100644 index f20e40fca..000000000 --- a/comfy/api/configuration.py +++ /dev/null @@ -1,2 +0,0 @@ -class Configuration: - pass \ No newline at end of file diff --git a/comfy/api/configurations/__init__.py b/comfy/api/configurations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/configurations/api_configuration.py b/comfy/api/configurations/api_configuration.py new file mode 100644 index 000000000..a4cbf879c --- /dev/null +++ b/comfy/api/configurations/api_configuration.py @@ -0,0 +1,281 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import copy +from http import client as http_client +import logging +import multiprocessing +import sys +import typing +import typing_extensions + +import urllib3 + +from comfy.api import exceptions +from comfy.api.servers import server_0 + +# the server to use at each openapi document json path +ServerInfo = typing.TypedDict( + 'ServerInfo', + { + 'servers/0': server_0.Server0, + }, + total=False +) + + +class ServerIndexInfoRequired(typing.TypedDict): + servers: typing.Literal[0] + +ServerIndexInfoOptional = typing.TypedDict( + 'ServerIndexInfoOptional', + { + }, + total=False +) + + +class ServerIndexInfo(ServerIndexInfoRequired, ServerIndexInfoOptional): + """ + the default server_index to use at each openapi document json path + the fallback value is stored in the 'servers' key + """ + + +class ApiConfiguration(object): + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator + + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + Do not edit the class manually. + + :param server_info: the servers that can be used to make endpoint calls + :param server_index_info: index to servers configuration + """ + + def __init__( + self, + server_info: typing.Optional[ServerInfo] = None, + server_index_info: typing.Optional[ServerIndexInfo] = None, + ): + """Constructor + """ + # Authentication Settings + self.security_scheme_info: typing.Dict[str, typing.Any] = {} + self.security_index_info = {'security': 0} + # Server Info + self.server_info: ServerInfo = server_info or { + 'servers/0': server_0.Server0(), + } + self.server_index_info: ServerIndexInfo = server_index_info or {'servers': 0} + self.logger = {} + """Logging Settings + """ + self.logger["package_logger"] = logging.getLogger("comfy.api") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + """Log format + """ + self.logger_stream_handler = None + """Log stream handler + """ + self.logger_file_handler = None + """Log file handler + """ + self.logger_file = None + """Debug file location + """ + self.debug = False + """Debug switch + """ + + self.verify_ssl = True + """SSL/TLS verification + Set this to false to skip verifying SSL certificate when calling API + from https server. + """ + self.ssl_ca_cert = None + """Set this to customize the certificate file to verify the peer. + """ + self.cert_file = None + """client certificate file + """ + self.key_file = None + """client key file + """ + self.assert_hostname = None + """Set this to True/False to enable/disable SSL hostname verification. + """ + + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + """urllib3 connection pool's maximum number of connections saved + per pool. urllib3 uses 1 connection as default value, but this is + not the best value when you are making a lot of possibly parallel + requests to the same host, which is often the case here. + cpu_count * 5 is used as default value to increase performance. + """ + + self.proxy = None + """Proxy URL + """ + self.proxy_headers = None + """Proxy headers + """ + self.safe_chars_for_path_param = '' + """Safe chars for path_param + """ + self.retries = None + """Adding retries to override urllib3 default value 3 + """ + # Enable client side validation + self.client_side_validation = True + + # Options to pass down to the underlying urllib3 socket + self.socket_options = None + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k not in ('logger', 'logger_file_handler'): + setattr(result, k, copy.deepcopy(v, memo)) + # shallow copy of loggers + result.logger = copy.copy(self.logger) + # use setters to configure loggers + result.logger_file = self.logger_file + result.debug = self.debug + return result + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in self.logger.items(): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in self.logger.items(): + logger.setLevel(logging.DEBUG) + # turn on http_client debug + http_client.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in self.logger.items(): + logger.setLevel(logging.WARNING) + # turn off http_client debug + http_client.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: 0.0.1\n"\ + "SDK Package Version: 1.0.0".\ + format(env=sys.platform, pyversion=sys.version) + + def get_server_url( + self, + key_prefix: typing.Literal[ + "servers", + ], + index: typing.Optional[int], + ) -> str: + """Gets host URL based on the index + :param index: array index of the host settings + :return: URL based on host settings + """ + if index: + used_index = index + else: + try: + used_index = self.server_index_info[key_prefix] + except KeyError: + # fallback and use the default index + used_index = self.server_index_info.get("servers", 0) + server_info_key = typing.cast( + typing.Literal[ + "servers/0", + ], + f"{key_prefix}/{used_index}" + ) + try: + server = self.server_info[server_info_key] + except KeyError as ex: + raise ex + return server.url diff --git a/comfy/api/configurations/schema_configuration.py b/comfy/api/configurations/schema_configuration.py new file mode 100644 index 000000000..66068bd71 --- /dev/null +++ b/comfy/api/configurations/schema_configuration.py @@ -0,0 +1,108 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import typing + +from comfy.api import exceptions + + +PYTHON_KEYWORD_TO_JSON_SCHEMA_KEYWORD = { + 'additional_properties': 'additionalProperties', + 'all_of': 'allOf', + 'any_of': 'anyOf', + 'const_value_to_name': 'const', + 'contains': 'contains', + 'dependent_required': 'dependentRequired', + 'dependent_schemas': 'dependentSchemas', + 'discriminator': 'discriminator', + # default omitted because it has no validation impact + 'else_': 'else', + 'enum_value_to_name': 'enum', + 'exclusive_maximum': 'exclusiveMaximum', + 'exclusive_minimum': 'exclusiveMinimum', + 'format': 'format', + 'if_': 'if', + 'inclusive_maximum': 'maximum', + 'inclusive_minimum': 'minimum', + 'items': 'items', + 'max_contains': 'maxContains', + 'max_items': 'maxItems', + 'max_length': 'maxLength', + 'max_properties': 'maxProperties', + 'min_contains': 'minContains', + 'min_items': 'minItems', + 'min_length': 'minLength', + 'min_properties': 'minProperties', + 'multiple_of': 'multipleOf', + 'not_': 'not', + 'one_of': 'oneOf', + 'pattern': 'pattern', + 'pattern_properties': 'patternProperties', + 'prefix_items': 'prefixItems', + 'properties': 'properties', + 'property_names': 'propertyNames', + 'required': 'required', + 'then': 'then', + 'types': 'type', + 'unique_items': 'uniqueItems', + 'unevaluated_items': 'unevaluatedItems', + 'unevaluated_properties': 'unevaluatedProperties' +} + +class SchemaConfiguration: + """NOTE: This class is auto generated by OpenAPI JSON Schema Generator + + Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator + Do not edit the class manually. + + :param disabled_json_schema_keywords (set): Set of + JSON schema validation keywords to disable JSON schema structural validation + rules. The following keywords may be specified: multipleOf, maximum, + exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern, + maxItems, minItems. + By default, the validation is performed for data generated locally by the client + and data received from the server, independent of any validation performed by + the server side. If the input data does not satisfy the JSON schema validation + rules specified in the OpenAPI document, an exception is raised. + If disabled_json_schema_keywords is set, structural validation is + disabled. This can be useful to troubleshoot data validation problem, such as + when the OpenAPI document validation rules do not match the actual API data + received by the server. + :param server_index: Index to servers configuration. + """ + + def __init__( + self, + disabled_json_schema_keywords = set(), + ): + """Constructor + """ + self.disabled_json_schema_keywords = disabled_json_schema_keywords + + @property + def disabled_json_schema_python_keywords(self) -> typing.Set[str]: + return self.__disabled_json_schema_python_keywords + + @property + def disabled_json_schema_keywords(self) -> typing.Set[str]: + return self.__disabled_json_schema_keywords + + @disabled_json_schema_keywords.setter + def disabled_json_schema_keywords(self, json_keywords: typing.Set[str]): + disabled_json_schema_keywords = set() + disabled_json_schema_python_keywords = set() + for k in json_keywords: + python_keywords = {key for key, val in PYTHON_KEYWORD_TO_JSON_SCHEMA_KEYWORD.items() if val == k} + if not python_keywords: + raise exceptions.ApiValueError( + "Invalid keyword: '{0}''".format(k)) + disabled_json_schema_keywords.add(k) + disabled_json_schema_python_keywords.update(python_keywords) + self.__disabled_json_schema_keywords = disabled_json_schema_keywords + self.__disabled_json_schema_python_keywords = disabled_json_schema_python_keywords \ No newline at end of file diff --git a/comfy/api/exceptions.py b/comfy/api/exceptions.py index b6463f586..9f53e5aac 100644 --- a/comfy/api/exceptions.py +++ b/comfy/api/exceptions.py @@ -1,22 +1,132 @@ -class OpenApiException: - pass +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import dataclasses +import typing + +from comfy.api import api_response -class ApiAttributeError: - pass +class OpenApiException(Exception): + """The base exception class for all OpenAPIExceptions""" + +def render_path(path_to_item): + """Returns a string representation of a path""" + result = "" + for pth in path_to_item: + if isinstance(pth, int): + result += "[{0}]".format(pth) + else: + result += "['{0}']".format(pth) + return result -class ApiTypeError: - pass +class ApiTypeError(OpenApiException, TypeError): + def __init__(self, msg, path_to_item=None, valid_classes=None, + key_type=None): + """ Raises an exception for TypeErrors + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list): a list of keys an indices to get to the + current_item + None if unset + valid_classes (tuple): the primitive classes that current item + should be an instance of + None if unset + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + None if unset + """ + self.path_to_item = path_to_item + self.valid_classes = valid_classes + self.key_type = key_type + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiTypeError, self).__init__(full_msg) -class ApiValueError: - pass +class ApiValueError(OpenApiException, ValueError): + def __init__(self, msg, path_to_item=None): + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list) the path to the exception in the + received_data dict. None if unset + """ + + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiValueError, self).__init__(full_msg) -class ApiKeyError: - pass +class ApiAttributeError(OpenApiException, AttributeError): + def __init__(self, msg, path_to_item=None): + """ + Raised when an attribute reference or assignment fails. + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiAttributeError, self).__init__(full_msg) -class ApiException: - pass \ No newline at end of file +class ApiKeyError(OpenApiException, KeyError): + def __init__(self, msg, path_to_item=None): + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiKeyError, self).__init__(full_msg) + +T = typing.TypeVar('T', bound=api_response.ApiResponse) + + +@dataclasses.dataclass +class ApiException(OpenApiException, typing.Generic[T]): + status: int + reason: typing.Optional[str] = None + api_response: typing.Optional[T] = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.reason) + if self.api_response: + if self.api_response.response.headers: + error_message += "HTTP response headers: {0}\n".format( + self.api_response.response.headers) + if self.api_response.response.data: + error_message += "HTTP response body: {0}\n".format(self.api_response.response.data) + + return error_message diff --git a/comfy/api/openapi.yaml b/comfy/api/openapi.yaml index e429ce82e..25af0c332 100644 --- a/comfy/api/openapi.yaml +++ b/comfy/api/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.0 +openapi: 3.1.0 info: title: comfyui version: 0.0.1 @@ -102,14 +102,35 @@ paths: name: subfolder schema: type: string + - in: query + name: channel + schema: + type: string + enum: + - 'rgba' + - 'rgb' + - 'a' responses: '200': description: Successful retrieval of file + headers: + Content-Disposition: + schema: + type: string + pattern: '^filename=".+"' content: image/png: schema: type: string format: binary + image/jpeg: + schema: + type: string + format: binary + image/webp: + schema: + type: string + format: binary '400': description: Bad Request '403': @@ -143,10 +164,19 @@ paths: $ref: "#/components/schemas/PromptRequest" responses: '200': - description: The prompt was queued. + description: The prompt was queued and a prompt ID was returned. content: + application/json: + example: + prompt_id: "some-value" + schema: + type: object + properties: + prompt_id: + description: The ID of the prompt that was queued + type: string text/plain: - example: "" + example: "some-value" schema: type: string '400': @@ -167,9 +197,7 @@ paths: schema: type: object additionalProperties: - type: array - items: - $ref: "#/components/schemas/Node" + $ref: "#/components/schemas/Node" /history: get: summary: (UI) Get history @@ -184,13 +212,21 @@ paths: additionalProperties: type: object properties: - timestamp: - type: number prompt: $ref: "#/components/schemas/QueueTuple" - # todo: do the outputs format outputs: + $ref: "#/components/schemas/Outputs" + status: type: object + properties: + status_str: + type: string + completed: + type: bool + messages: + type: array + items: + type: string post: summary: (UI) Post history operationId: post_history @@ -254,6 +290,35 @@ paths: responses: '200': description: OK + /free: + # from 6d281b4ff4ad3918a4f3b4ca4a8b547a2ba3bf80 + post: + summary: (UI) Unload models or free memory + operationId: free + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + properties: + unload_models: + type: boolean + enum: [ true ] + required: + - unload_models + additionalProperties: false + - type: object + properties: + free_memory: + type: boolean + enum: [ true ] + required: + - free_memory + additionalProperties: false + description: >- + A POST request to /free with: {"unload_models":true} will unload models from vram. + A POST request to /free with: {"free_memory":true} will unload models and free all cached data from the last run workflow. /api/v1/images/{digest}: get: summary: (API) Get image @@ -327,9 +392,16 @@ paths: example: filename=ComfyUI_00001.png schema: type: string + pattern: '^filename=.+' description: | The content of the last SaveImage node. content: + image/png: + schema: + description: | + Binary image data. This will be the first SaveImage node in the workflow. + type: string + format: binary application/json: schema: description: | @@ -419,6 +491,17 @@ paths: The server is too busy to process this request right now. This should only be returned by a load balancer. Standalone comfyui does not return this. + parameters: + - in: header + name: Accept + schema: + type: string + enum: + - "application/json" + - "image/png" + required: false + description: | + Specifies the media type the client is willing to receive. requestBody: content: application/json: @@ -482,16 +565,25 @@ components: type: array items: type: string + output_is_list: + description: Indicates if the output of the corresponding index as the item in this array is a list output. + type: array + items: + type: boolean output_name: type: array items: type: string name: type: string + display_name: + type: string description: type: string category: type: string + output_node: + type: boolean ExtraData: type: object properties: @@ -698,19 +790,25 @@ components: oneOf: - type: number - type: string + - type: boolean - type: array description: | When this is specified, it is a node connection, followed by an output. items: minItems: 2 maxItems: 2 - oneOf: + prefixItems: - type: string - type: integer description: The inputs for the node, which can be scalar values or references to other nodes' outputs. is_changed: - type: string - description: A string representing whether the node has changed (optional). + oneOf: + - type: array + description: An array of hashes representing whether the node has changed (optional). + items: + type: string + - type: string + description: A string representing whether the node has changed (optional). Workflow: type: object properties: @@ -814,14 +912,47 @@ components: QueueTuple: type: array description: | - The first item is the queue priority - The second item is the hash id of the prompt object - The third item is a Prompt - The fourth item is an ExtraData - items: - minItems: 4 - maxItems: 4 - oneOf: - - type: number - - $ref: "#/components/schemas/Prompt" - - $ref: "#/components/schemas/ExtraData" \ No newline at end of file + An item that was added to the queue. + minItems: 3 + maxItems: 5 + prefixItems: + - type: number + description: Queue priority. + - type: string + description: The hash id of the prompt object. This should be the the prompt ID. + - $ref: "#/components/schemas/Prompt" + - $ref: "#/components/schemas/ExtraData" + - type: array + description: A list of "good output" node IDs in the prompt. + items: + type: string + Outputs: + description: | + The keys are node IDs, the values are output objects + type: object + additionalProperties: + $ref: "#/components/schemas/Output" + Output: + type: object + properties: + images: + type: array + items: + $ref: "#/components/schemas/FileOutput" + latents: + type: array + items: + $ref: "#/components/schemas/FileOutput" + FileOutput: + type: object + required: + - filename + - subfolder + - type + properties: + filename: + type: string + subfolder: + type: string + type: + type: string \ No newline at end of file diff --git a/comfy/api/openapi_python_config.yaml b/comfy/api/openapi_python_config.yaml index f79647935..3ae7ad44c 100644 --- a/comfy/api/openapi_python_config.yaml +++ b/comfy/api/openapi_python_config.yaml @@ -3,10 +3,10 @@ outputDir: ./ generatorName: python globalProperties: supportingFiles: - - "__init__.py" - "schemas.py" - "exceptions.py" - "configuration.py" + - "api_response.py" additionalProperties: generateSourceCodeOnly: true packageName: comfy.api diff --git a/comfy/api/paths/__init__.py b/comfy/api/paths/__init__.py new file mode 100644 index 000000000..7b833d91e --- /dev/null +++ b/comfy/api/paths/__init__.py @@ -0,0 +1,3 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis import path_to_api diff --git a/comfy/api/paths/api_v1_images_digest/__init__.py b/comfy/api/paths/api_v1_images_digest/__init__.py new file mode 100644 index 000000000..8412160c2 --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.api_v1_images_digest import ApiV1ImagesDigest + +path = "/api/v1/images/{digest}" \ No newline at end of file diff --git a/comfy/api/paths/api_v1_images_digest/get/__init__.py b/comfy/api/paths/api_v1_images_digest/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_images_digest/get/operation.py b/comfy/api/paths/api_v1_images_digest/get/operation.py new file mode 100644 index 000000000..6176dccd3 --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/operation.py @@ -0,0 +1,162 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import ( + response_200, + response_404, +) +from .parameters import parameter_0 +from .path_parameters import PathParameters, PathParametersDictInput, PathParametersDict +path_parameter_classes = ( + parameter_0.Parameter0, +) + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '404': typing.Type[response_404.ResponseFor404], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '404': response_404.ResponseFor404, +} +_non_error_status_codes = frozenset({ + '200', +}) +_error_status_codes = frozenset({ + '404', +}) + +_all_accept_content_types = ( + "image/png", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _api_v1_images_digest_get( + self, + path_params: typing.Union[ + PathParametersDictInput, + PathParametersDict + ], + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _api_v1_images_digest_get( + self, + path_params: typing.Union[ + PathParametersDictInput, + PathParametersDict + ], + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _api_v1_images_digest_get( + self, + path_params: typing.Union[ + PathParametersDictInput, + PathParametersDict + ], + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (API) Get image + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + path_params = PathParameters.validate( + path_params, + configuration=self.api_client.schema_configuration + ) + used_path, query_params_suffix = self._get_used_path( + path, + path_parameters=path_parameter_classes, + path_params=path_params, + skip_validation=True + ) + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '404', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class ApiV1ImagesDigestGet(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + api_v1_images_digest_get = BaseApi._api_v1_images_digest_get + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._api_v1_images_digest_get diff --git a/comfy/api/paths/api_v1_images_digest/get/parameters/__init__.py b/comfy/api/paths/api_v1_images_digest/get/parameters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/__init__.py b/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/__init__.py new file mode 100644 index 000000000..408c7e64b --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/__init__.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Parameter0(api_client.PathParameter): + name = "digest" + style = api_client.ParameterStyle.SIMPLE + schema: typing_extensions.TypeAlias = schema.Schema + required = True diff --git a/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/schema.py b/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/parameters/parameter_0/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/api_v1_images_digest/get/path_parameters.py b/comfy/api/paths/api_v1_images_digest/get/path_parameters.py new file mode 100644 index 000000000..154f29f91 --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/path_parameters.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +AdditionalProperties: typing_extensions.TypeAlias = schemas.NotAnyTypeSchema + +from comfy.api.paths.api_v1_images_digest.get.parameters.parameter_0 import schema +Properties = typing.TypedDict( + 'Properties', + { + "digest": typing.Type[schema.Schema], + } +) + + +class PathParametersDict(schemas.immutabledict[str, str]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + "digest", + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + + def __new__( + cls, + *, + digest: str, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + arg_: typing.Dict[str, typing.Any] = { + "digest": digest, + } + used_arg_ = typing.cast(PathParametersDictInput, arg_) + return PathParameters.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + PathParametersDictInput, + PathParametersDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PathParametersDict: + return PathParameters.validate(arg, configuration=configuration) + + @property + def digest(self) -> str: + return self.__getitem__("digest") +PathParametersDictInput = typing.TypedDict( + 'PathParametersDictInput', + { + "digest": str, + } +) + + +@dataclasses.dataclass(frozen=True) +class PathParameters( + schemas.Schema[PathParametersDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + required: typing.FrozenSet[str] = frozenset({ + "digest", + }) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: PathParametersDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + PathParametersDictInput, + PathParametersDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> PathParametersDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/__init__.py b/comfy/api/paths/api_v1_images_digest/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/response_200/__init__.py b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/__init__.py new file mode 100644 index 000000000..2c64b88e5 --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.image_png import schema as image_png_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: typing.Union[bytes, schemas.FileIO] + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ImagePngMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = image_png_schema.Schema + content = { + 'image/png': ImagePngMediaType, + } diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/__init__.py b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/__init__.py b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/schema.py b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/schema.py new file mode 100644 index 000000000..580119c44 --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/responses/response_200/content/image_png/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.BinarySchema diff --git a/comfy/api/paths/api_v1_images_digest/get/responses/response_404/__init__.py b/comfy/api/paths/api_v1_images_digest/get/responses/response_404/__init__.py new file mode 100644 index 000000000..3cbb7f03d --- /dev/null +++ b/comfy/api/paths/api_v1_images_digest/get/responses/response_404/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor404(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/__init__.py b/comfy/api/paths/api_v1_prompts/__init__.py new file mode 100644 index 000000000..2c6745dd3 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.api_v1_prompts import ApiV1Prompts + +path = "/api/v1/prompts" \ No newline at end of file diff --git a/comfy/api/paths/api_v1_prompts/get/__init__.py b/comfy/api/paths/api_v1_prompts/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/get/operation.py b/comfy/api/paths/api_v1_prompts/get/operation.py new file mode 100644 index 000000000..3180b70b0 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/get/operation.py @@ -0,0 +1,136 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import ( + response_200, + response_404, +) + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '404': typing.Type[response_404.ResponseFor404], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '404': response_404.ResponseFor404, +} +_non_error_status_codes = frozenset({ + '200', +}) +_error_status_codes = frozenset({ + '404', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _api_v1_prompts_get( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _api_v1_prompts_get( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _api_v1_prompts_get( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (API) Get prompt + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '404', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class ApiV1PromptsGet(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + api_v1_prompts_get = BaseApi._api_v1_prompts_get + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._api_v1_prompts_get diff --git a/comfy/api/paths/api_v1_prompts/get/responses/__init__.py b/comfy/api/paths/api_v1_prompts/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/get/responses/response_200/__init__.py b/comfy/api/paths/api_v1_prompts/get/responses/response_200/__init__.py new file mode 100644 index 000000000..6342574ce --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.prompt.PromptDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema2 + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/__init__.py b/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..7fdd686de --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + + +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.components.schema import prompt +Schema2: typing_extensions.TypeAlias = prompt.Prompt diff --git a/comfy/api/paths/api_v1_prompts/get/responses/response_404/__init__.py b/comfy/api/paths/api_v1_prompts/get/responses/response_404/__init__.py new file mode 100644 index 000000000..3cbb7f03d --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/get/responses/response_404/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor404(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/__init__.py b/comfy/api/paths/api_v1_prompts/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/operation.py b/comfy/api/paths/api_v1_prompts/post/operation.py new file mode 100644 index 000000000..443799e04 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/operation.py @@ -0,0 +1,240 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.components.schema import prompt +from comfy.api.paths.api_v1_prompts.post.request_body.content.multipart_formdata import schema + +from .. import path +from .responses import ( + response_200, + response_204, + response_400, + response_429, + response_500, + response_503, + response_507, +) +from . import request_body + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '204': typing.Type[response_204.ResponseFor204], + '400': typing.Type[response_400.ResponseFor400], + '429': typing.Type[response_429.ResponseFor429], + '500': typing.Type[response_500.ResponseFor500], + '503': typing.Type[response_503.ResponseFor503], + '507': typing.Type[response_507.ResponseFor507], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '204': response_204.ResponseFor204, + '400': response_400.ResponseFor400, + '429': response_429.ResponseFor429, + '500': response_500.ResponseFor500, + '503': response_503.ResponseFor503, + '507': response_507.ResponseFor507, +} +_non_error_status_codes = frozenset({ + '200', + '204', +}) +_error_status_codes = frozenset({ + '400', + '429', + '500', + '507', + '503', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _api_v1_prompts_post( + self, + body: typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["application/json"] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> typing.Union[ + response_200.ApiResponse, + response_204.ApiResponse, + ]: ... + + @typing.overload + def _api_v1_prompts_post( + self, + body: typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["application/json"] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + @typing.overload + def _api_v1_prompts_post( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["multipart/formdata"], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> typing.Union[ + response_200.ApiResponse, + response_204.ApiResponse, + ]: ... + + @typing.overload + def _api_v1_prompts_post( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["multipart/formdata"], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _api_v1_prompts_post( + self, + body: typing.Union[ + typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + ], + typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + ], + schemas.Unset, + ] = schemas.unset, + *, + skip_deserialization: bool = False, + content_type: typing.Literal[ + "application/json", + "multipart/formdata", + ] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (API) Generate image + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + + fields, serialized_body = self._get_fields_and_body( + request_body=request_body.RequestBody, + body=body, + content_type=content_type, + headers=headers + ) + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + headers=headers, + fields=fields, + body=serialized_body, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + '204', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '400', + '429', + '500', + '507', + '503', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class ApiV1PromptsPost(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + api_v1_prompts_post = BaseApi._api_v1_prompts_post + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._api_v1_prompts_post diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/__init__.py b/comfy/api/paths/api_v1_prompts/post/request_body/__init__.py new file mode 100644 index 000000000..c80c1dc76 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/request_body/__init__.py @@ -0,0 +1,25 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema +from .content.multipart_formdata import schema as multipart_formdata_schema + + +class RequestBody(api_client.RequestBody): + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema2 + + + class MultipartFormdataMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = multipart_formdata_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + 'multipart/formdata': MultipartFormdataMediaType, + } diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/content/__init__.py b/comfy/api/paths/api_v1_prompts/post/request_body/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/__init__.py b/comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/schema.py b/comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/schema.py new file mode 100644 index 000000000..7fdd686de --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/request_body/content/application_json/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + + +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.components.schema import prompt +Schema2: typing_extensions.TypeAlias = prompt.Prompt diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/__init__.py b/comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/schema.py b/comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/schema.py new file mode 100644 index 000000000..7711cfa90 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/request_body/content/multipart_formdata/schema.py @@ -0,0 +1,186 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Items: typing_extensions.TypeAlias = schemas.BinarySchema + + +class FilesTuple( + typing.Tuple[ + typing.Union[bytes, schemas.FileIO], + ... + ] +): + + def __new__(cls, arg: typing.Union[FilesTupleInput, FilesTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Files.validate(arg, configuration=configuration) +FilesTupleInput = typing.Union[ + typing.List[ + typing.Union[ + bytes, + io.FileIO, + io.BufferedReader, + schemas.FileIO + ], + ], + typing.Tuple[ + typing.Union[ + bytes, + io.FileIO, + io.BufferedReader, + schemas.FileIO + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Files( + schemas.Schema[schemas.immutabledict, FilesTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: FilesTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + FilesTupleInput, + FilesTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> FilesTuple: + return super().validate_base( + arg, + configuration=configuration, + ) + +from comfy.api.components.schema import prompt +Properties = typing.TypedDict( + 'Properties', + { + "prompt": typing.Type[prompt.Prompt], + "files": typing.Type[Files], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "prompt", + "files", + }) + + def __new__( + cls, + *, + prompt: typing.Union[ + prompt.PromptDictInput, + prompt.PromptDict, + schemas.Unset + ] = schemas.unset, + files: typing.Union[ + FilesTupleInput, + FilesTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("prompt", prompt), + ("files", files), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def prompt(self) -> typing.Union[prompt.PromptDict, schemas.Unset]: + val = self.get("prompt", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + prompt.PromptDict, + val + ) + + @property + def files(self) -> typing.Union[FilesTuple, schemas.Unset]: + val = self.get("files", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + FilesTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/api_v1_prompts/post/responses/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/__init__.py new file mode 100644 index 000000000..67b3fb383 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/__init__.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema +from .headers import header_digest +from .headers import header_content_disposition +from .headers import header_location +from . import header_parameters +parameters: typing.Dict[str, typing.Type[api_client.HeaderParameterWithoutName]] = { + 'Digest': header_digest.Digest, + 'Content-Disposition': header_content_disposition.ContentDisposition, + 'Location': header_location.Location, +} + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: header_parameters.HeadersDict + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } + headers=parameters + headers_schema = header_parameters.Headers diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/schema.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..eff0c6bf0 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/content/application_json/schema.py @@ -0,0 +1,156 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Items: typing_extensions.TypeAlias = schemas.StrSchema + + +class UrlsTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[UrlsTupleInput, UrlsTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Urls.validate(arg, configuration=configuration) +UrlsTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Urls( + schemas.Schema[schemas.immutabledict, UrlsTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: UrlsTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + UrlsTupleInput, + UrlsTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> UrlsTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties = typing.TypedDict( + 'Properties', + { + "urls": typing.Type[Urls], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "urls", + }) + + def __new__( + cls, + *, + urls: typing.Union[ + UrlsTupleInput, + UrlsTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("urls", urls), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def urls(self) -> typing.Union[UrlsTuple, schemas.Unset]: + val = self.get("urls", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + UrlsTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/header_parameters.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/header_parameters.py new file mode 100644 index 000000000..0f40c51b1 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/header_parameters.py @@ -0,0 +1,131 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +AdditionalProperties: typing_extensions.TypeAlias = schemas.NotAnyTypeSchema + +from comfy.api.paths.api_v1_prompts.post.responses.response_200.headers.header_content_disposition import schema as schema_2 +from comfy.api.paths.api_v1_prompts.post.responses.response_200.headers.header_digest import schema +from comfy.api.paths.api_v1_prompts.post.responses.response_200.headers.header_location import schema as schema_3 +Properties = typing.TypedDict( + 'Properties', + { + "Digest": typing.Type[schema.Schema], + "Content-Disposition": typing.Type[schema_2.Schema], + "Location": typing.Type[schema_3.Schema], + } +) + + +class HeadersDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "Digest", + "Content-Disposition", + "Location", + }) + + def __new__( + cls, + *, + Digest: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + Location: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("Digest", Digest), + ("Location", Location), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + used_arg_ = typing.cast(HeadersDictInput, arg_) + return Headers.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + HeadersDictInput, + HeadersDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> HeadersDict: + return Headers.validate(arg, configuration=configuration) + + @property + def Digest(self) -> typing.Union[str, schemas.Unset]: + val = self.get("Digest", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def Location(self) -> typing.Union[str, schemas.Unset]: + val = self.get("Location", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) +HeadersDictInput = typing.TypedDict( + 'HeadersDictInput', + { + "Digest": str, + "Content-Disposition": str, + "Location": str, + }, + total=False +) + + +@dataclasses.dataclass(frozen=True) +class Headers( + schemas.Schema[HeadersDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: HeadersDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + HeadersDictInput, + HeadersDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> HeadersDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/__init__.py new file mode 100644 index 000000000..ce9858421 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/__init__.py @@ -0,0 +1,14 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class ContentDisposition(api_client.HeaderParameterWithoutName): + style = api_client.ParameterStyle.SIMPLE + schema: typing_extensions.TypeAlias = schema.Schema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/schema.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_content_disposition/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/__init__.py new file mode 100644 index 000000000..70f1e3994 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/__init__.py @@ -0,0 +1,14 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Digest(api_client.HeaderParameterWithoutName): + style = api_client.ParameterStyle.SIMPLE + schema: typing_extensions.TypeAlias = schema.Schema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/schema.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_digest/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/__init__.py new file mode 100644 index 000000000..3533c7df7 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/__init__.py @@ -0,0 +1,14 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Location(api_client.HeaderParameterWithoutName): + style = api_client.ParameterStyle.SIMPLE + schema: typing_extensions.TypeAlias = schema.Schema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/schema.py b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_200/headers/header_location/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_204/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_204/__init__.py new file mode 100644 index 000000000..f31ffeb31 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_204/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor204(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_400/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_400/__init__.py new file mode 100644 index 000000000..633b58511 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_400/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor400(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_429/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_429/__init__.py new file mode 100644 index 000000000..47d08ea6c --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_429/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor429(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_500/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_500/__init__.py new file mode 100644 index 000000000..6db077309 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_500/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor500(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_503/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_503/__init__.py new file mode 100644 index 000000000..c750e27e8 --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_503/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor503(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/api_v1_prompts/post/responses/response_507/__init__.py b/comfy/api/paths/api_v1_prompts/post/responses/response_507/__init__.py new file mode 100644 index 000000000..61766c0ea --- /dev/null +++ b/comfy/api/paths/api_v1_prompts/post/responses/response_507/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor507(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/embeddings/__init__.py b/comfy/api/paths/embeddings/__init__.py new file mode 100644 index 000000000..b63e8787d --- /dev/null +++ b/comfy/api/paths/embeddings/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.embeddings import Embeddings + +path = "/embeddings" \ No newline at end of file diff --git a/comfy/api/paths/embeddings/get/__init__.py b/comfy/api/paths/embeddings/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/embeddings/get/operation.py b/comfy/api/paths/embeddings/get/operation.py new file mode 100644 index 000000000..8b1680a76 --- /dev/null +++ b/comfy/api/paths/embeddings/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_embeddings( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_embeddings( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_embeddings( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get embeddings + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetEmbeddings(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_embeddings = BaseApi._get_embeddings + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_embeddings diff --git a/comfy/api/paths/embeddings/get/responses/__init__.py b/comfy/api/paths/embeddings/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/embeddings/get/responses/response_200/__init__.py b/comfy/api/paths/embeddings/get/responses/response_200/__init__.py new file mode 100644 index 000000000..eb105534c --- /dev/null +++ b/comfy/api/paths/embeddings/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaTuple + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/embeddings/get/responses/response_200/content/__init__.py b/comfy/api/paths/embeddings/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/embeddings/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/embeddings/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/embeddings/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/embeddings/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..ce689b335 --- /dev/null +++ b/comfy/api/paths/embeddings/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,63 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Items: typing_extensions.TypeAlias = schemas.StrSchema + + +class SchemaTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[SchemaTupleInput, SchemaTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Schema.validate(arg, configuration=configuration) +SchemaTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[schemas.immutabledict, SchemaTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: SchemaTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaTupleInput, + SchemaTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaTuple: + return super().validate_base( + arg, + configuration=configuration, + ) diff --git a/comfy/api/paths/extensions/__init__.py b/comfy/api/paths/extensions/__init__.py new file mode 100644 index 000000000..f8144feeb --- /dev/null +++ b/comfy/api/paths/extensions/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.extensions import Extensions + +path = "/extensions" \ No newline at end of file diff --git a/comfy/api/paths/extensions/get/__init__.py b/comfy/api/paths/extensions/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/extensions/get/operation.py b/comfy/api/paths/extensions/get/operation.py new file mode 100644 index 000000000..1678d7a65 --- /dev/null +++ b/comfy/api/paths/extensions/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_extensions( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_extensions( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_extensions( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get extensions + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetExtensions(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_extensions = BaseApi._get_extensions + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_extensions diff --git a/comfy/api/paths/extensions/get/responses/__init__.py b/comfy/api/paths/extensions/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/extensions/get/responses/response_200/__init__.py b/comfy/api/paths/extensions/get/responses/response_200/__init__.py new file mode 100644 index 000000000..eb105534c --- /dev/null +++ b/comfy/api/paths/extensions/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaTuple + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/extensions/get/responses/response_200/content/__init__.py b/comfy/api/paths/extensions/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/extensions/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/extensions/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/extensions/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/extensions/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..ce689b335 --- /dev/null +++ b/comfy/api/paths/extensions/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,63 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Items: typing_extensions.TypeAlias = schemas.StrSchema + + +class SchemaTuple( + typing.Tuple[ + str, + ... + ] +): + + def __new__(cls, arg: typing.Union[SchemaTupleInput, SchemaTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Schema.validate(arg, configuration=configuration) +SchemaTupleInput = typing.Union[ + typing.List[ + str, + ], + typing.Tuple[ + str, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[schemas.immutabledict, SchemaTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: SchemaTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaTupleInput, + SchemaTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaTuple: + return super().validate_base( + arg, + configuration=configuration, + ) diff --git a/comfy/api/paths/history/__init__.py b/comfy/api/paths/history/__init__.py new file mode 100644 index 000000000..adc63d9c2 --- /dev/null +++ b/comfy/api/paths/history/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.history import History + +path = "/history" \ No newline at end of file diff --git a/comfy/api/paths/history/get/__init__.py b/comfy/api/paths/history/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/get/operation.py b/comfy/api/paths/history/get/operation.py new file mode 100644 index 000000000..270d45e9f --- /dev/null +++ b/comfy/api/paths/history/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_history( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_history( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_history( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get history + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetHistory(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_history = BaseApi._get_history + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_history diff --git a/comfy/api/paths/history/get/responses/__init__.py b/comfy/api/paths/history/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/get/responses/response_200/__init__.py b/comfy/api/paths/history/get/responses/response_200/__init__.py new file mode 100644 index 000000000..fa4f0dcb1 --- /dev/null +++ b/comfy/api/paths/history/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/history/get/responses/response_200/content/__init__.py b/comfy/api/paths/history/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/history/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/history/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..6ac60f8f6 --- /dev/null +++ b/comfy/api/paths/history/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,221 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Timestamp: typing_extensions.TypeAlias = schemas.NumberSchema +Outputs: typing_extensions.TypeAlias = schemas.DictSchema + +from comfy.api.components.schema import queue_tuple +Properties = typing.TypedDict( + 'Properties', + { + "timestamp": typing.Type[Timestamp], + "prompt": typing.Type[queue_tuple.QueueTuple], + "outputs": typing.Type[Outputs], + } +) + + +class AdditionalPropertiesDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "timestamp", + "prompt", + "outputs", + }) + + def __new__( + cls, + *, + timestamp: typing.Union[ + int, + float, + schemas.Unset + ] = schemas.unset, + prompt: typing.Union[ + queue_tuple.QueueTupleTupleInput, + queue_tuple.QueueTupleTuple, + schemas.Unset + ] = schemas.unset, + outputs: typing.Union[ + typing.Mapping[str, schemas.INPUT_TYPES_ALL], + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("timestamp", timestamp), + ("prompt", prompt), + ("outputs", outputs), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(AdditionalPropertiesDictInput, arg_) + return AdditionalProperties.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + AdditionalPropertiesDictInput, + AdditionalPropertiesDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> AdditionalPropertiesDict: + return AdditionalProperties.validate(arg, configuration=configuration) + + @property + def timestamp(self) -> typing.Union[int, float, schemas.Unset]: + val = self.get("timestamp", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[int, float], + val + ) + + @property + def prompt(self) -> typing.Union[queue_tuple.QueueTupleTuple, schemas.Unset]: + val = self.get("prompt", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + queue_tuple.QueueTupleTuple, + val + ) + + @property + def outputs(self) -> typing.Union[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], schemas.Unset]: + val = self.get("outputs", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +AdditionalPropertiesDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class AdditionalProperties( + schemas.Schema[AdditionalPropertiesDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: AdditionalPropertiesDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + AdditionalPropertiesDictInput, + AdditionalPropertiesDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> AdditionalPropertiesDict: + return super().validate_base( + arg, + configuration=configuration, + ) + + + +class SchemaDict(schemas.immutabledict[str, AdditionalPropertiesDict]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + AdditionalPropertiesDictInput, + AdditionalPropertiesDict, + ], + ): + used_kwargs = typing.cast(SchemaDictInput, kwargs) + return Schema.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[AdditionalPropertiesDict, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + AdditionalPropertiesDict, + val + ) +SchemaDictInput = typing.Mapping[ + str, + typing.Union[ + AdditionalPropertiesDictInput, + AdditionalPropertiesDict, + ], +] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/history/post/__init__.py b/comfy/api/paths/history/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/post/operation.py b/comfy/api/paths/history/post/operation.py new file mode 100644 index 000000000..a86f2cc37 --- /dev/null +++ b/comfy/api/paths/history/post/operation.py @@ -0,0 +1,136 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.paths.history.post.request_body.content.application_json import schema + +from .. import path +from .responses import response_200 +from . import request_body + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + + +class BaseApi(api_client.Api): + @typing.overload + def _post_history( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _post_history( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _post_history( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: bool = False, + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Post history + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers() + # TODO add cookie handling + + fields, serialized_body = self._get_fields_and_body( + request_body=request_body.RequestBody, + body=body, + content_type=content_type, + headers=headers + ) + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + headers=headers, + fields=fields, + body=serialized_body, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class PostHistory(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + post_history = BaseApi._post_history + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._post_history diff --git a/comfy/api/paths/history/post/request_body/__init__.py b/comfy/api/paths/history/post/request_body/__init__.py new file mode 100644 index 000000000..d2218ab55 --- /dev/null +++ b/comfy/api/paths/history/post/request_body/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +class RequestBody(api_client.RequestBody): + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/history/post/request_body/content/__init__.py b/comfy/api/paths/history/post/request_body/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/post/request_body/content/application_json/__init__.py b/comfy/api/paths/history/post/request_body/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/post/request_body/content/application_json/schema.py b/comfy/api/paths/history/post/request_body/content/application_json/schema.py new file mode 100644 index 000000000..691cac8f6 --- /dev/null +++ b/comfy/api/paths/history/post/request_body/content/application_json/schema.py @@ -0,0 +1,174 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Clear: typing_extensions.TypeAlias = schemas.BoolSchema +Items: typing_extensions.TypeAlias = schemas.IntSchema + + +class DeleteTuple( + typing.Tuple[ + int, + ... + ] +): + + def __new__(cls, arg: typing.Union[DeleteTupleInput, DeleteTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Delete.validate(arg, configuration=configuration) +DeleteTupleInput = typing.Union[ + typing.List[ + int, + ], + typing.Tuple[ + int, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Delete( + schemas.Schema[schemas.immutabledict, DeleteTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: DeleteTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + DeleteTupleInput, + DeleteTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> DeleteTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties = typing.TypedDict( + 'Properties', + { + "clear": typing.Type[Clear], + "delete": typing.Type[Delete], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "clear", + "delete", + }) + + def __new__( + cls, + *, + clear: typing.Union[ + bool, + schemas.Unset + ] = schemas.unset, + delete: typing.Union[ + DeleteTupleInput, + DeleteTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("clear", clear), + ("delete", delete), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def clear(self) -> typing.Union[bool, schemas.Unset]: + val = self.get("clear", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + bool, + val + ) + + @property + def delete(self) -> typing.Union[DeleteTuple, schemas.Unset]: + val = self.get("delete", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + DeleteTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/history/post/responses/__init__.py b/comfy/api/paths/history/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/history/post/responses/response_200/__init__.py b/comfy/api/paths/history/post/responses/response_200/__init__.py new file mode 100644 index 000000000..c5e693a02 --- /dev/null +++ b/comfy/api/paths/history/post/responses/response_200/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/interrupt/__init__.py b/comfy/api/paths/interrupt/__init__.py new file mode 100644 index 000000000..8628bc8a7 --- /dev/null +++ b/comfy/api/paths/interrupt/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.interrupt import Interrupt + +path = "/interrupt" \ No newline at end of file diff --git a/comfy/api/paths/interrupt/post/__init__.py b/comfy/api/paths/interrupt/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/interrupt/post/operation.py b/comfy/api/paths/interrupt/post/operation.py new file mode 100644 index 000000000..0b8e61021 --- /dev/null +++ b/comfy/api/paths/interrupt/post/operation.py @@ -0,0 +1,105 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + + +class BaseApi(api_client.Api): + @typing.overload + def _post_interrupt( + self, + *, + skip_deserialization: typing.Literal[False] = False, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _post_interrupt( + self, + *, + skip_deserialization: typing.Literal[True], + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _post_interrupt( + self, + *, + skip_deserialization: bool = False, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Post interrupt + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class PostInterrupt(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + post_interrupt = BaseApi._post_interrupt + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._post_interrupt diff --git a/comfy/api/paths/interrupt/post/responses/__init__.py b/comfy/api/paths/interrupt/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/interrupt/post/responses/response_200/__init__.py b/comfy/api/paths/interrupt/post/responses/response_200/__init__.py new file mode 100644 index 000000000..c5e693a02 --- /dev/null +++ b/comfy/api/paths/interrupt/post/responses/response_200/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/object_info/__init__.py b/comfy/api/paths/object_info/__init__.py new file mode 100644 index 000000000..4da254ecf --- /dev/null +++ b/comfy/api/paths/object_info/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.object_info import ObjectInfo + +path = "/object_info" \ No newline at end of file diff --git a/comfy/api/paths/object_info/get/__init__.py b/comfy/api/paths/object_info/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/object_info/get/operation.py b/comfy/api/paths/object_info/get/operation.py new file mode 100644 index 000000000..5c2c5785b --- /dev/null +++ b/comfy/api/paths/object_info/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_object_info( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_object_info( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_object_info( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get object info + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetObjectInfo(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_object_info = BaseApi._get_object_info + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_object_info diff --git a/comfy/api/paths/object_info/get/responses/__init__.py b/comfy/api/paths/object_info/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/object_info/get/responses/response_200/__init__.py b/comfy/api/paths/object_info/get/responses/response_200/__init__.py new file mode 100644 index 000000000..fa4f0dcb1 --- /dev/null +++ b/comfy/api/paths/object_info/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/object_info/get/responses/response_200/content/__init__.py b/comfy/api/paths/object_info/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/object_info/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/object_info/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/object_info/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/object_info/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..4a07f10b0 --- /dev/null +++ b/comfy/api/paths/object_info/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,90 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +from comfy.api.components.schema import node + + +class SchemaDict(schemas.immutabledict[str, node.NodeDict]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + }) + def __new__( + cls, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: typing.Union[ + node.NodeDictInput, + node.NodeDict, + ], + ): + used_kwargs = typing.cast(SchemaDictInput, kwargs) + return Schema.validate(used_kwargs, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + def get_additional_property_(self, name: str) -> typing.Union[node.NodeDict, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + val = self.get(name, schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + node.NodeDict, + val + ) +SchemaDictInput = typing.Mapping[ + str, + typing.Union[ + node.NodeDictInput, + node.NodeDict, + ], +] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + additional_properties: typing.Type[node.Node] = dataclasses.field(default_factory=lambda: node.Node) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/prompt/__init__.py b/comfy/api/paths/prompt/__init__.py new file mode 100644 index 000000000..52811c701 --- /dev/null +++ b/comfy/api/paths/prompt/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.prompt import Prompt + +path = "/prompt" \ No newline at end of file diff --git a/comfy/api/paths/prompt/get/__init__.py b/comfy/api/paths/prompt/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/get/operation.py b/comfy/api/paths/prompt/get/operation.py new file mode 100644 index 000000000..d8b4a94ec --- /dev/null +++ b/comfy/api/paths/prompt/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_prompt( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_prompt( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_prompt( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get queue info + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetPrompt(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_prompt = BaseApi._get_prompt + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_prompt diff --git a/comfy/api/paths/prompt/get/responses/__init__.py b/comfy/api/paths/prompt/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/get/responses/response_200/__init__.py b/comfy/api/paths/prompt/get/responses/response_200/__init__.py new file mode 100644 index 000000000..fa4f0dcb1 --- /dev/null +++ b/comfy/api/paths/prompt/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/prompt/get/responses/response_200/content/__init__.py b/comfy/api/paths/prompt/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/prompt/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/prompt/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..89e95dc8b --- /dev/null +++ b/comfy/api/paths/prompt/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,198 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +QueueRemaining: typing_extensions.TypeAlias = schemas.IntSchema +Properties = typing.TypedDict( + 'Properties', + { + "queue_remaining": typing.Type[QueueRemaining], + } +) + + +class ExecInfoDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "queue_remaining", + }) + + def __new__( + cls, + *, + queue_remaining: typing.Union[ + int, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("queue_remaining", queue_remaining), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(ExecInfoDictInput, arg_) + return ExecInfo.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + ExecInfoDictInput, + ExecInfoDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExecInfoDict: + return ExecInfo.validate(arg, configuration=configuration) + + @property + def queue_remaining(self) -> typing.Union[int, schemas.Unset]: + val = self.get("queue_remaining", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + int, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +ExecInfoDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class ExecInfo( + schemas.Schema[ExecInfoDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: ExecInfoDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + ExecInfoDictInput, + ExecInfoDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> ExecInfoDict: + return super().validate_base( + arg, + configuration=configuration, + ) + +Properties2 = typing.TypedDict( + 'Properties2', + { + "exec_info": typing.Type[ExecInfo], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "exec_info", + }) + + def __new__( + cls, + *, + exec_info: typing.Union[ + ExecInfoDictInput, + ExecInfoDict, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("exec_info", exec_info), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def exec_info(self) -> typing.Union[ExecInfoDict, schemas.Unset]: + val = self.get("exec_info", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + ExecInfoDict, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties2 = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties2)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/prompt/post/__init__.py b/comfy/api/paths/prompt/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/operation.py b/comfy/api/paths/prompt/post/operation.py new file mode 100644 index 000000000..35ed6b9b6 --- /dev/null +++ b/comfy/api/paths/prompt/post/operation.py @@ -0,0 +1,165 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.components.schema import prompt_request + +from .. import path +from .responses import ( + response_200, + response_400, +) +from . import request_body + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '400': typing.Type[response_400.ResponseFor400], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '400': response_400.ResponseFor400, +} +_non_error_status_codes = frozenset({ + '200', +}) +_error_status_codes = frozenset({ + '400', +}) + +_all_accept_content_types = ( + "text/plain", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _post_prompt( + self, + body: typing.Union[ + prompt_request.PromptRequestDictInput, + prompt_request.PromptRequestDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["application/json"] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _post_prompt( + self, + body: typing.Union[ + prompt_request.PromptRequestDictInput, + prompt_request.PromptRequestDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["application/json"] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _post_prompt( + self, + body: typing.Union[ + prompt_request.PromptRequestDictInput, + prompt_request.PromptRequestDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: bool = False, + content_type: typing.Literal["application/json"] = "application/json", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Post prompt + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + + fields, serialized_body = self._get_fields_and_body( + request_body=request_body.RequestBody, + body=body, + content_type=content_type, + headers=headers + ) + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + headers=headers, + fields=fields, + body=serialized_body, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '400', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class PostPrompt(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + post_prompt = BaseApi._post_prompt + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._post_prompt diff --git a/comfy/api/paths/prompt/post/request_body/__init__.py b/comfy/api/paths/prompt/post/request_body/__init__.py new file mode 100644 index 000000000..2d3c77475 --- /dev/null +++ b/comfy/api/paths/prompt/post/request_body/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +class RequestBody(api_client.RequestBody): + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema2 + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/prompt/post/request_body/content/__init__.py b/comfy/api/paths/prompt/post/request_body/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/request_body/content/application_json/__init__.py b/comfy/api/paths/prompt/post/request_body/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/request_body/content/application_json/schema.py b/comfy/api/paths/prompt/post/request_body/content/application_json/schema.py new file mode 100644 index 000000000..480e0579c --- /dev/null +++ b/comfy/api/paths/prompt/post/request_body/content/application_json/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + + +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.components.schema import prompt_request +Schema2: typing_extensions.TypeAlias = prompt_request.PromptRequest diff --git a/comfy/api/paths/prompt/post/responses/__init__.py b/comfy/api/paths/prompt/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/responses/response_200/__init__.py b/comfy/api/paths/prompt/post/responses/response_200/__init__.py new file mode 100644 index 000000000..6b3d4d2a0 --- /dev/null +++ b/comfy/api/paths/prompt/post/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.text_plain import schema as text_plain_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: str + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class TextPlainMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = text_plain_schema.Schema + content = { + 'text/plain': TextPlainMediaType, + } diff --git a/comfy/api/paths/prompt/post/responses/response_200/content/__init__.py b/comfy/api/paths/prompt/post/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/responses/response_200/content/text_plain/__init__.py b/comfy/api/paths/prompt/post/responses/response_200/content/text_plain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/responses/response_200/content/text_plain/schema.py b/comfy/api/paths/prompt/post/responses/response_200/content/text_plain/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/prompt/post/responses/response_200/content/text_plain/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/prompt/post/responses/response_400/__init__.py b/comfy/api/paths/prompt/post/responses/response_400/__init__.py new file mode 100644 index 000000000..5df7efc90 --- /dev/null +++ b/comfy/api/paths/prompt/post/responses/response_400/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.text_plain import schema as text_plain_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: str + headers: schemas.Unset + + +class ResponseFor400(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class TextPlainMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = text_plain_schema.Schema + content = { + 'text/plain': TextPlainMediaType, + } diff --git a/comfy/api/paths/prompt/post/responses/response_400/content/__init__.py b/comfy/api/paths/prompt/post/responses/response_400/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/responses/response_400/content/text_plain/__init__.py b/comfy/api/paths/prompt/post/responses/response_400/content/text_plain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/prompt/post/responses/response_400/content/text_plain/schema.py b/comfy/api/paths/prompt/post/responses/response_400/content/text_plain/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/prompt/post/responses/response_400/content/text_plain/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/queue/__init__.py b/comfy/api/paths/queue/__init__.py new file mode 100644 index 000000000..0bcf6368f --- /dev/null +++ b/comfy/api/paths/queue/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.queue import Queue + +path = "/queue" \ No newline at end of file diff --git a/comfy/api/paths/queue/get/__init__.py b/comfy/api/paths/queue/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/get/operation.py b/comfy/api/paths/queue/get/operation.py new file mode 100644 index 000000000..ea3041837 --- /dev/null +++ b/comfy/api/paths/queue/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_queue( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_queue( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_queue( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Get queue + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetQueue(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_queue = BaseApi._get_queue + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_queue diff --git a/comfy/api/paths/queue/get/responses/__init__.py b/comfy/api/paths/queue/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/get/responses/response_200/__init__.py b/comfy/api/paths/queue/get/responses/response_200/__init__.py new file mode 100644 index 000000000..fa4f0dcb1 --- /dev/null +++ b/comfy/api/paths/queue/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/queue/get/responses/response_200/content/__init__.py b/comfy/api/paths/queue/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/get/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/queue/get/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/get/responses/response_200/content/application_json/schema.py b/comfy/api/paths/queue/get/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..c58566fce --- /dev/null +++ b/comfy/api/paths/queue/get/responses/response_200/content/application_json/schema.py @@ -0,0 +1,237 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +from comfy.api.components.schema import queue_tuple + + +class QueueRunningTuple( + typing.Tuple[ + queue_tuple.QueueTupleTuple, + ... + ] +): + + def __new__(cls, arg: typing.Union[QueueRunningTupleInput, QueueRunningTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return QueueRunning.validate(arg, configuration=configuration) +QueueRunningTupleInput = typing.Union[ + typing.List[ + typing.Union[ + queue_tuple.QueueTupleTupleInput, + queue_tuple.QueueTupleTuple + ], + ], + typing.Tuple[ + typing.Union[ + queue_tuple.QueueTupleTupleInput, + queue_tuple.QueueTupleTuple + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class QueueRunning( + schemas.Schema[schemas.immutabledict, QueueRunningTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[queue_tuple.QueueTuple] = dataclasses.field(default_factory=lambda: queue_tuple.QueueTuple) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: QueueRunningTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + QueueRunningTupleInput, + QueueRunningTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> QueueRunningTuple: + return super().validate_base( + arg, + configuration=configuration, + ) + + +class QueuePendingTuple( + typing.Tuple[ + queue_tuple.QueueTupleTuple, + ... + ] +): + + def __new__(cls, arg: typing.Union[QueuePendingTupleInput, QueuePendingTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return QueuePending.validate(arg, configuration=configuration) +QueuePendingTupleInput = typing.Union[ + typing.List[ + typing.Union[ + queue_tuple.QueueTupleTupleInput, + queue_tuple.QueueTupleTuple + ], + ], + typing.Tuple[ + typing.Union[ + queue_tuple.QueueTupleTupleInput, + queue_tuple.QueueTupleTuple + ], + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class QueuePending( + schemas.Schema[schemas.immutabledict, QueuePendingTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[queue_tuple.QueueTuple] = dataclasses.field(default_factory=lambda: queue_tuple.QueueTuple) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: QueuePendingTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + QueuePendingTupleInput, + QueuePendingTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> QueuePendingTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties = typing.TypedDict( + 'Properties', + { + "queue_running": typing.Type[QueueRunning], + "queue_pending": typing.Type[QueuePending], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "queue_running", + "queue_pending", + }) + + def __new__( + cls, + *, + queue_running: typing.Union[ + QueueRunningTupleInput, + QueueRunningTuple, + schemas.Unset + ] = schemas.unset, + queue_pending: typing.Union[ + QueuePendingTupleInput, + QueuePendingTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("queue_running", queue_running), + ("queue_pending", queue_pending), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def queue_running(self) -> typing.Union[QueueRunningTuple, schemas.Unset]: + val = self.get("queue_running", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + QueueRunningTuple, + val + ) + + @property + def queue_pending(self) -> typing.Union[QueuePendingTuple, schemas.Unset]: + val = self.get("queue_pending", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + QueuePendingTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/queue/post/__init__.py b/comfy/api/paths/queue/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/post/operation.py b/comfy/api/paths/queue/post/operation.py new file mode 100644 index 000000000..e95d6c49b --- /dev/null +++ b/comfy/api/paths/queue/post/operation.py @@ -0,0 +1,136 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.paths.queue.post.request_body.content.application_json import schema + +from .. import path +from .responses import response_200 +from . import request_body + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + + +class BaseApi(api_client.Api): + @typing.overload + def _post_queue( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _post_queue( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _post_queue( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: bool = False, + content_type: typing.Literal["application/json"] = "application/json", + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Post queue + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers() + # TODO add cookie handling + + fields, serialized_body = self._get_fields_and_body( + request_body=request_body.RequestBody, + body=body, + content_type=content_type, + headers=headers + ) + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + headers=headers, + fields=fields, + body=serialized_body, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class PostQueue(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + post_queue = BaseApi._post_queue + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._post_queue diff --git a/comfy/api/paths/queue/post/request_body/__init__.py b/comfy/api/paths/queue/post/request_body/__init__.py new file mode 100644 index 000000000..d2218ab55 --- /dev/null +++ b/comfy/api/paths/queue/post/request_body/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +class RequestBody(api_client.RequestBody): + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/queue/post/request_body/content/__init__.py b/comfy/api/paths/queue/post/request_body/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/post/request_body/content/application_json/__init__.py b/comfy/api/paths/queue/post/request_body/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/post/request_body/content/application_json/schema.py b/comfy/api/paths/queue/post/request_body/content/application_json/schema.py new file mode 100644 index 000000000..691cac8f6 --- /dev/null +++ b/comfy/api/paths/queue/post/request_body/content/application_json/schema.py @@ -0,0 +1,174 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Clear: typing_extensions.TypeAlias = schemas.BoolSchema +Items: typing_extensions.TypeAlias = schemas.IntSchema + + +class DeleteTuple( + typing.Tuple[ + int, + ... + ] +): + + def __new__(cls, arg: typing.Union[DeleteTupleInput, DeleteTuple], configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None): + return Delete.validate(arg, configuration=configuration) +DeleteTupleInput = typing.Union[ + typing.List[ + int, + ], + typing.Tuple[ + int, + ... + ] +] + + +@dataclasses.dataclass(frozen=True) +class Delete( + schemas.Schema[schemas.immutabledict, DeleteTuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + items: typing.Type[Items] = dataclasses.field(default_factory=lambda: Items) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + tuple: DeleteTuple + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + DeleteTupleInput, + DeleteTuple, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> DeleteTuple: + return super().validate_base( + arg, + configuration=configuration, + ) +Properties = typing.TypedDict( + 'Properties', + { + "clear": typing.Type[Clear], + "delete": typing.Type[Delete], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "clear", + "delete", + }) + + def __new__( + cls, + *, + clear: typing.Union[ + bool, + schemas.Unset + ] = schemas.unset, + delete: typing.Union[ + DeleteTupleInput, + DeleteTuple, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("clear", clear), + ("delete", delete), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def clear(self) -> typing.Union[bool, schemas.Unset]: + val = self.get("clear", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + bool, + val + ) + + @property + def delete(self) -> typing.Union[DeleteTuple, schemas.Unset]: + val = self.get("delete", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + DeleteTuple, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/queue/post/responses/__init__.py b/comfy/api/paths/queue/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/queue/post/responses/response_200/__init__.py b/comfy/api/paths/queue/post/responses/response_200/__init__.py new file mode 100644 index 000000000..c5e693a02 --- /dev/null +++ b/comfy/api/paths/queue/post/responses/response_200/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/solidus/__init__.py b/comfy/api/paths/solidus/__init__.py new file mode 100644 index 000000000..8602e89d7 --- /dev/null +++ b/comfy/api/paths/solidus/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.solidus import Solidus + +path = "/" \ No newline at end of file diff --git a/comfy/api/paths/solidus/get/__init__.py b/comfy/api/paths/solidus/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/solidus/get/operation.py b/comfy/api/paths/solidus/get/operation.py new file mode 100644 index 000000000..d5cd4b3c8 --- /dev/null +++ b/comfy/api/paths/solidus/get/operation.py @@ -0,0 +1,114 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import response_200 + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, +} +_non_error_status_codes = frozenset({ + '200', +}) + +_all_accept_content_types = ( + "text/html", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _get_root( + self, + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _get_root( + self, + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _get_root( + self, + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) index.html + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class GetRoot(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + get_root = BaseApi._get_root + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._get_root diff --git a/comfy/api/paths/solidus/get/responses/__init__.py b/comfy/api/paths/solidus/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/solidus/get/responses/response_200/__init__.py b/comfy/api/paths/solidus/get/responses/response_200/__init__.py new file mode 100644 index 000000000..060423980 --- /dev/null +++ b/comfy/api/paths/solidus/get/responses/response_200/__init__.py @@ -0,0 +1,27 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class TextHtmlMediaType(api_client.MediaType): + pass + content = { + 'text/html': TextHtmlMediaType, + } diff --git a/comfy/api/paths/upload_image/__init__.py b/comfy/api/paths/upload_image/__init__.py new file mode 100644 index 000000000..fe4f77680 --- /dev/null +++ b/comfy/api/paths/upload_image/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.upload_image import UploadImage + +path = "/upload/image" \ No newline at end of file diff --git a/comfy/api/paths/upload_image/post/__init__.py b/comfy/api/paths/upload_image/post/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/operation.py b/comfy/api/paths/upload_image/post/operation.py new file mode 100644 index 000000000..30bddee40 --- /dev/null +++ b/comfy/api/paths/upload_image/post/operation.py @@ -0,0 +1,165 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] +from comfy.api.paths.upload_image.post.request_body.content.multipart_form_data import schema + +from .. import path +from .responses import ( + response_200, + response_400, +) +from . import request_body + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '400': typing.Type[response_400.ResponseFor400], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '400': response_400.ResponseFor400, +} +_non_error_status_codes = frozenset({ + '200', +}) +_error_status_codes = frozenset({ + '400', +}) + +_all_accept_content_types = ( + "application/json", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _upload_image( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[False] = False, + content_type: typing.Literal["multipart/form-data"] = "multipart/form-data", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _upload_image( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: typing.Literal[True], + content_type: typing.Literal["multipart/form-data"] = "multipart/form-data", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _upload_image( + self, + body: typing.Union[ + schema.SchemaDictInput, + schema.SchemaDict, + schemas.Unset + ] = schemas.unset, + *, + skip_deserialization: bool = False, + content_type: typing.Literal["multipart/form-data"] = "multipart/form-data", + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) Upload an image. + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + used_path = path + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + + fields, serialized_body = self._get_fields_and_body( + request_body=request_body.RequestBody, + body=body, + content_type=content_type, + headers=headers + ) + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + method='post', + host=host, + headers=headers, + fields=fields, + body=serialized_body, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '400', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class UploadImage(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + upload_image = BaseApi._upload_image + + +class ApiForPost(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + post = BaseApi._upload_image diff --git a/comfy/api/paths/upload_image/post/request_body/__init__.py b/comfy/api/paths/upload_image/post/request_body/__init__.py new file mode 100644 index 000000000..055836eb5 --- /dev/null +++ b/comfy/api/paths/upload_image/post/request_body/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.multipart_form_data import schema as multipart_form_data_schema + + +class RequestBody(api_client.RequestBody): + + + class MultipartFormDataMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = multipart_form_data_schema.Schema + content = { + 'multipart/form-data': MultipartFormDataMediaType, + } diff --git a/comfy/api/paths/upload_image/post/request_body/content/__init__.py b/comfy/api/paths/upload_image/post/request_body/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/__init__.py b/comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/schema.py b/comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/schema.py new file mode 100644 index 000000000..f545516db --- /dev/null +++ b/comfy/api/paths/upload_image/post/request_body/content/multipart_form_data/schema.py @@ -0,0 +1,108 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Image: typing_extensions.TypeAlias = schemas.BinarySchema +Properties = typing.TypedDict( + 'Properties', + { + "image": typing.Type[Image], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "image", + }) + + def __new__( + cls, + *, + image: typing.Union[ + bytes, + io.FileIO, + io.BufferedReader, + schemas.FileIO, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("image", image), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def image(self) -> typing.Union[bytes, schemas.FileIO, schemas.Unset]: + val = self.get("image", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Union[bytes, schemas.FileIO], + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/upload_image/post/responses/__init__.py b/comfy/api/paths/upload_image/post/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/responses/response_200/__init__.py b/comfy/api/paths/upload_image/post/responses/response_200/__init__.py new file mode 100644 index 000000000..fa4f0dcb1 --- /dev/null +++ b/comfy/api/paths/upload_image/post/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.application_json import schema as application_json_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: application_json_schema.SchemaDict + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ApplicationJsonMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = application_json_schema.Schema + content = { + 'application/json': ApplicationJsonMediaType, + } diff --git a/comfy/api/paths/upload_image/post/responses/response_200/content/__init__.py b/comfy/api/paths/upload_image/post/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/responses/response_200/content/application_json/__init__.py b/comfy/api/paths/upload_image/post/responses/response_200/content/application_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/upload_image/post/responses/response_200/content/application_json/schema.py b/comfy/api/paths/upload_image/post/responses/response_200/content/application_json/schema.py new file mode 100644 index 000000000..ce33c9b3c --- /dev/null +++ b/comfy/api/paths/upload_image/post/responses/response_200/content/application_json/schema.py @@ -0,0 +1,105 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Name: typing_extensions.TypeAlias = schemas.StrSchema +Properties = typing.TypedDict( + 'Properties', + { + "name": typing.Type[Name], + } +) + + +class SchemaDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "name", + }) + + def __new__( + cls, + *, + name: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + **kwargs: schemas.INPUT_TYPES_ALL, + ): + arg_: typing.Dict[str, typing.Any] = {} + for key_, val in ( + ("name", name), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + arg_.update(kwargs) + used_arg_ = typing.cast(SchemaDictInput, arg_) + return Schema.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + SchemaDictInput, + SchemaDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return Schema.validate(arg, configuration=configuration) + + @property + def name(self) -> typing.Union[str, schemas.Unset]: + val = self.get("name", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + def get_additional_property_(self, name: str) -> typing.Union[schemas.OUTPUT_BASE_TYPES, schemas.Unset]: + schemas.raise_if_key_known(name, self.__required_keys__, self.__optional_keys__) + return self.get(name, schemas.unset) +SchemaDictInput = typing.Mapping[str, schemas.INPUT_TYPES_ALL] + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema[SchemaDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: SchemaDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + SchemaDictInput, + SchemaDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> SchemaDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/upload_image/post/responses/response_400/__init__.py b/comfy/api/paths/upload_image/post/responses/response_400/__init__.py new file mode 100644 index 000000000..633b58511 --- /dev/null +++ b/comfy/api/paths/upload_image/post/responses/response_400/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor400(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/view/__init__.py b/comfy/api/paths/view/__init__.py new file mode 100644 index 000000000..e7bfe26c5 --- /dev/null +++ b/comfy/api/paths/view/__init__.py @@ -0,0 +1,5 @@ +# do not import all endpoints into this module because that uses a lot of memory and stack frames +# if you need the ability to import all endpoints from this module, import them with +# from comfy.api.apis.paths.view import View + +path = "/view" \ No newline at end of file diff --git a/comfy/api/paths/view/get/__init__.py b/comfy/api/paths/view/get/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/view/get/operation.py b/comfy/api/paths/view/get/operation.py new file mode 100644 index 000000000..a50a83d00 --- /dev/null +++ b/comfy/api/paths/view/get/operation.py @@ -0,0 +1,179 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api import api_client, exceptions +from comfy.api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .. import path +from .responses import ( + response_200, + response_400, + response_403, + response_404, +) +from .parameters import ( + parameter_0, + parameter_1, + parameter_2, +) +from .query_parameters import QueryParameters, QueryParametersDictInput, QueryParametersDict +query_parameter_classes = ( + parameter_0.Parameter0, + parameter_1.Parameter1, + parameter_2.Parameter2, +) + + +__StatusCodeToResponse = typing.TypedDict( + '__StatusCodeToResponse', + { + '200': typing.Type[response_200.ResponseFor200], + '400': typing.Type[response_400.ResponseFor400], + '403': typing.Type[response_403.ResponseFor403], + '404': typing.Type[response_404.ResponseFor404], + } +) +_status_code_to_response: __StatusCodeToResponse = { + '200': response_200.ResponseFor200, + '400': response_400.ResponseFor400, + '403': response_403.ResponseFor403, + '404': response_404.ResponseFor404, +} +_non_error_status_codes = frozenset({ + '200', +}) +_error_status_codes = frozenset({ + '400', + '403', + '404', +}) + +_all_accept_content_types = ( + "image/png", +) + + +class BaseApi(api_client.Api): + @typing.overload + def _view_image( + self, + query_params: typing.Union[ + QueryParametersDictInput, + QueryParametersDict + ], + *, + skip_deserialization: typing.Literal[False] = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> response_200.ApiResponse: ... + + @typing.overload + def _view_image( + self, + query_params: typing.Union[ + QueryParametersDictInput, + QueryParametersDict + ], + *, + skip_deserialization: typing.Literal[True], + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> api_response.ApiResponseWithoutDeserialization: ... + + def _view_image( + self, + query_params: typing.Union[ + QueryParametersDictInput, + QueryParametersDict + ], + *, + skip_deserialization: bool = False, + accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types, + server_index: typing.Optional[int] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ): + """ + (UI) View image + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + query_params = QueryParameters.validate( + query_params, + configuration=self.api_client.schema_configuration + ) + used_path, query_params_suffix = self._get_used_path( + path, + query_parameters=query_parameter_classes, + query_params=query_params, + skip_validation=True + ) + headers = self._get_headers(accept_content_types=accept_content_types) + # TODO add cookie handling + host = self.api_client.configuration.get_server_url( + "servers", server_index + ) + + raw_response = self.api_client.call_api( + resource_path=used_path, + query_params_suffix=query_params_suffix, + method='get', + host=host, + headers=headers, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(skip_deser_response) + return skip_deser_response + + status = str(raw_response.status) + if status in _non_error_status_codes: + status_code = typing.cast( + typing.Literal[ + '200', + ], + status + ) + return _status_code_to_response[status_code].deserialize( + raw_response, self.api_client.schema_configuration) + elif status in _error_status_codes: + error_status_code = typing.cast( + typing.Literal[ + '400', + '403', + '404', + ], + status + ) + error_response = _status_code_to_response[error_status_code].deserialize( + raw_response, self.api_client.schema_configuration) + raise exceptions.ApiException( + status=error_response.response.status, + reason=error_response.response.reason, + api_response=error_response + ) + + response = api_response.ApiResponseWithoutDeserialization(response=raw_response) + self._verify_response_status(response) + return response + + +class ViewImage(BaseApi): + # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names + view_image = BaseApi._view_image + + +class ApiForGet(BaseApi): + # this class is used by api classes that refer to endpoints by path and http method names + get = BaseApi._view_image diff --git a/comfy/api/paths/view/get/parameters/__init__.py b/comfy/api/paths/view/get/parameters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/view/get/parameters/parameter_0/__init__.py b/comfy/api/paths/view/get/parameters/parameter_0/__init__.py new file mode 100644 index 000000000..f4a859277 --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_0/__init__.py @@ -0,0 +1,17 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Parameter0(api_client.QueryParameter): + name = "filename" + style = api_client.ParameterStyle.FORM + schema: typing_extensions.TypeAlias = schema.Schema + required = True + explode = True diff --git a/comfy/api/paths/view/get/parameters/parameter_0/schema.py b/comfy/api/paths/view/get/parameters/parameter_0/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_0/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/view/get/parameters/parameter_1/__init__.py b/comfy/api/paths/view/get/parameters/parameter_1/__init__.py new file mode 100644 index 000000000..cd6989b9d --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_1/__init__.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Parameter1(api_client.QueryParameter): + name = "type" + style = api_client.ParameterStyle.FORM + schema: typing_extensions.TypeAlias = schema.Schema + explode = True diff --git a/comfy/api/paths/view/get/parameters/parameter_1/schema.py b/comfy/api/paths/view/get/parameters/parameter_1/schema.py new file mode 100644 index 000000000..e406907e0 --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_1/schema.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + + +class SchemaEnums: + + @schemas.classproperty + def OUTPUT(cls) -> typing.Literal["output"]: + return Schema.validate("output") + + @schemas.classproperty + def INPUT(cls) -> typing.Literal["input"]: + return Schema.validate("input") + + @schemas.classproperty + def TEMP(cls) -> typing.Literal["temp"]: + return Schema.validate("temp") + + +@dataclasses.dataclass(frozen=True) +class Schema( + schemas.Schema +): + types: typing.FrozenSet[typing.Type] = frozenset({ + str, + }) + enum_value_to_name: typing.Mapping[typing.Union[int, float, str, schemas.Bool, None], str] = dataclasses.field( + default_factory=lambda: { + "output": "OUTPUT", + "input": "INPUT", + "temp": "TEMP", + } + ) + enums = SchemaEnums + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal["output"], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal["output"]: ... + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal["input"], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal["input"]: ... + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal["temp"], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal["temp"]: ... + @typing.overload + @classmethod + def validate( + cls, + arg: str, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal["output","input","temp",]: ... + @classmethod + def validate( + cls, + arg: typing.Union[str, datetime.date, datetime.datetime, uuid.UUID], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[ + "output", + "input", + "temp", + ]: + validated_arg = super().validate_base( + arg, + configuration=configuration, + ) + return typing.cast(typing.Literal[ + "output", + "input", + "temp", + ], + validated_arg + ) diff --git a/comfy/api/paths/view/get/parameters/parameter_2/__init__.py b/comfy/api/paths/view/get/parameters/parameter_2/__init__.py new file mode 100644 index 000000000..fe7fca589 --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_2/__init__.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.header_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from . import schema + + +class Parameter2(api_client.QueryParameter): + name = "subfolder" + style = api_client.ParameterStyle.FORM + schema: typing_extensions.TypeAlias = schema.Schema + explode = True diff --git a/comfy/api/paths/view/get/parameters/parameter_2/schema.py b/comfy/api/paths/view/get/parameters/parameter_2/schema.py new file mode 100644 index 000000000..b9146a9df --- /dev/null +++ b/comfy/api/paths/view/get/parameters/parameter_2/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.StrSchema diff --git a/comfy/api/paths/view/get/query_parameters.py b/comfy/api/paths/view/get/query_parameters.py new file mode 100644 index 000000000..8d1afb1ee --- /dev/null +++ b/comfy/api/paths/view/get/query_parameters.py @@ -0,0 +1,161 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +AdditionalProperties: typing_extensions.TypeAlias = schemas.NotAnyTypeSchema + +from comfy.api.paths.view.get.parameters.parameter_0 import schema +from comfy.api.paths.view.get.parameters.parameter_1 import schema as schema_3 +from comfy.api.paths.view.get.parameters.parameter_2 import schema as schema_2 +Properties = typing.TypedDict( + 'Properties', + { + "filename": typing.Type[schema.Schema], + "subfolder": typing.Type[schema_2.Schema], + "type": typing.Type[schema_3.Schema], + } +) +QueryParametersRequiredDictInput = typing.TypedDict( + 'QueryParametersRequiredDictInput', + { + "filename": str, + } +) +QueryParametersOptionalDictInput = typing.TypedDict( + 'QueryParametersOptionalDictInput', + { + "subfolder": str, + "type": typing.Literal[ + "output", + "input", + "temp" + ], + }, + total=False +) + + +class QueryParametersDict(schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES]): + + __required_keys__: typing.FrozenSet[str] = frozenset({ + "filename", + }) + __optional_keys__: typing.FrozenSet[str] = frozenset({ + "subfolder", + "type", + }) + + def __new__( + cls, + *, + filename: str, + subfolder: typing.Union[ + str, + schemas.Unset + ] = schemas.unset, + type: typing.Union[ + typing.Literal[ + "output", + "input", + "temp" + ], + schemas.Unset + ] = schemas.unset, + configuration_: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + arg_: typing.Dict[str, typing.Any] = { + "filename": filename, + } + for key_, val in ( + ("subfolder", subfolder), + ("type", type), + ): + if isinstance(val, schemas.Unset): + continue + arg_[key_] = val + used_arg_ = typing.cast(QueryParametersDictInput, arg_) + return QueryParameters.validate(used_arg_, configuration=configuration_) + + @staticmethod + def from_dict_( + arg: typing.Union[ + QueryParametersDictInput, + QueryParametersDict + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> QueryParametersDict: + return QueryParameters.validate(arg, configuration=configuration) + + @property + def filename(self) -> str: + return typing.cast( + str, + self.__getitem__("filename") + ) + + @property + def subfolder(self) -> typing.Union[str, schemas.Unset]: + val = self.get("subfolder", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + str, + val + ) + + @property + def type(self) -> typing.Union[typing.Literal["output", "input", "temp"], schemas.Unset]: + val = self.get("type", schemas.unset) + if isinstance(val, schemas.Unset): + return val + return typing.cast( + typing.Literal["output", "input", "temp"], + val + ) + + +class QueryParametersDictInput(QueryParametersRequiredDictInput, QueryParametersOptionalDictInput): + pass + + +@dataclasses.dataclass(frozen=True) +class QueryParameters( + schemas.Schema[QueryParametersDict, tuple] +): + types: typing.FrozenSet[typing.Type] = frozenset({schemas.immutabledict}) + required: typing.FrozenSet[str] = frozenset({ + "filename", + }) + properties: Properties = dataclasses.field(default_factory=lambda: schemas.typed_dict_to_instance(Properties)) # type: ignore + additional_properties: typing.Type[AdditionalProperties] = dataclasses.field(default_factory=lambda: AdditionalProperties) # type: ignore + type_to_output_cls: typing.Mapping[ + typing.Type, + typing.Type + ] = dataclasses.field( + default_factory=lambda: { + schemas.immutabledict: QueryParametersDict + } + ) + + @classmethod + def validate( + cls, + arg: typing.Union[ + QueryParametersDictInput, + QueryParametersDict, + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> QueryParametersDict: + return super().validate_base( + arg, + configuration=configuration, + ) + diff --git a/comfy/api/paths/view/get/responses/__init__.py b/comfy/api/paths/view/get/responses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/view/get/responses/response_200/__init__.py b/comfy/api/paths/view/get/responses/response_200/__init__.py new file mode 100644 index 000000000..2c64b88e5 --- /dev/null +++ b/comfy/api/paths/view/get/responses/response_200/__init__.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +from .content.image_png import schema as image_png_schema + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: typing.Union[bytes, schemas.FileIO] + headers: schemas.Unset + + +class ResponseFor200(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) + + + class ImagePngMediaType(api_client.MediaType): + schema: typing_extensions.TypeAlias = image_png_schema.Schema + content = { + 'image/png': ImagePngMediaType, + } diff --git a/comfy/api/paths/view/get/responses/response_200/content/__init__.py b/comfy/api/paths/view/get/responses/response_200/content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/view/get/responses/response_200/content/image_png/__init__.py b/comfy/api/paths/view/get/responses/response_200/content/image_png/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/paths/view/get/responses/response_200/content/image_png/schema.py b/comfy/api/paths/view/get/responses/response_200/content/image_png/schema.py new file mode 100644 index 000000000..580119c44 --- /dev/null +++ b/comfy/api/paths/view/get/responses/response_200/content/image_png/schema.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +from comfy.api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + +Schema: typing_extensions.TypeAlias = schemas.BinarySchema diff --git a/comfy/api/paths/view/get/responses/response_400/__init__.py b/comfy/api/paths/view/get/responses/response_400/__init__.py new file mode 100644 index 000000000..633b58511 --- /dev/null +++ b/comfy/api/paths/view/get/responses/response_400/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor400(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/view/get/responses/response_403/__init__.py b/comfy/api/paths/view/get/responses/response_403/__init__.py new file mode 100644 index 000000000..662d784db --- /dev/null +++ b/comfy/api/paths/view/get/responses/response_403/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor403(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/paths/view/get/responses/response_404/__init__.py b/comfy/api/paths/view/get/responses/response_404/__init__.py new file mode 100644 index 000000000..3cbb7f03d --- /dev/null +++ b/comfy/api/paths/view/get/responses/response_404/__init__.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass(frozen=True) +class ApiResponse(api_response.ApiResponse): + body: schemas.Unset + headers: schemas.Unset + + +class ResponseFor404(api_client.OpenApiResponse[ApiResponse]): + @classmethod + def get_response(cls, response, headers, body) -> ApiResponse: + return ApiResponse(response=response, body=body, headers=headers) diff --git a/comfy/api/py.typed b/comfy/api/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/rest.py b/comfy/api/rest.py new file mode 100644 index 000000000..b7c493881 --- /dev/null +++ b/comfy/api/rest.py @@ -0,0 +1,270 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import logging +import ssl +from urllib.parse import urlencode +import typing + +import certifi # type: ignore[import] +import urllib3 +from urllib3 import fields +from urllib3 import exceptions as urllib3_exceptions +from urllib3._collections import HTTPHeaderDict + +from comfy.api import exceptions + + +logger = logging.getLogger(__name__) +_TYPE_FIELD_VALUE = typing.Union[str, bytes] + + +class RequestField(fields.RequestField): + def __init__( + self, + name: str, + data: _TYPE_FIELD_VALUE, + filename: typing.Optional[str] = None, + headers: typing.Optional[typing.Mapping[str, typing.Union[str, None]]] = None, + header_formatter: typing.Optional[typing.Callable[[str, _TYPE_FIELD_VALUE], str]] = None, + ): + super().__init__(name, data, filename, headers, header_formatter) # type: ignore + + def __eq__(self, other): + if not isinstance(other, fields.RequestField): + return False + return self.__dict__ == other.__dict__ + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if configuration.retries is not None: + addition_pool_args['retries'] = configuration.retries + + if configuration.socket_options is not None: + addition_pool_args['socket_options'] = configuration.socket_options + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy: + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + proxy_headers=configuration.proxy_headers, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request( + self, + method: str, + url: str, + headers: typing.Optional[HTTPHeaderDict] = None, + fields: typing.Optional[typing.Tuple[RequestField, ...]] = None, + body: typing.Optional[typing.Union[str, bytes]] = None, + stream: bool = False, + timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + ) -> urllib3.HTTPResponse: + """Perform requests. + + :param method: http request method + :param url: http request url + :param headers: http request headers + :param body: request body, for other types + :param fields: request parameters for + `application/x-www-form-urlencoded` + or `multipart/form-data` + :param stream: if True, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is False. + :param timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if fields and body: + raise exceptions.ApiValueError( + "body parameter cannot be used with fields parameter." + ) + + headers = headers or HTTPHeaderDict() + + used_timeout: typing.Optional[urllib3.Timeout] = None + if timeout: + if isinstance(timeout, (int, float)): + used_timeout = urllib3.Timeout(total=timeout) + elif (isinstance(timeout, tuple) and + len(timeout) == 2): + used_timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1]) + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in {'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE'}: + if 'Content-Type' not in headers and body is None: + r = self.pool_manager.request( + method, + url, + preload_content=not stream, + timeout=used_timeout, + headers=headers + ) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + body=body, + encode_multipart=False, + preload_content=not stream, + timeout=used_timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=fields, + encode_multipart=True, + preload_content=not stream, + timeout=used_timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str) or isinstance(body, bytes): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=not stream, + timeout=used_timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise exceptions.ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + preload_content=not stream, + timeout=used_timeout, + headers=headers) + except urllib3_exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise exceptions.ApiException(status=0, reason=msg) + + if not stream: + # log response body + logger.debug("response body: %s", r.data) + + return r + + def get(self, url, headers=None, stream=False, + timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("GET", url, + headers=headers, + stream=stream, + timeout=timeout, + fields=fields) + + def head(self, url, headers=None, stream=False, + timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("HEAD", url, + headers=headers, + stream=stream, + timeout=timeout, + fields=fields) + + def options(self, url, headers=None, + body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("OPTIONS", url, + headers=headers, + stream=stream, + timeout=timeout, + body=body, fields=fields) + + def delete(self, url, headers=None, body=None, + stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("DELETE", url, + headers=headers, + stream=stream, + timeout=timeout, + body=body, fields=fields) + + def post(self, url, headers=None, + body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("POST", url, + headers=headers, + stream=stream, + timeout=timeout, + body=body, fields=fields) + + def put(self, url, headers=None, + body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("PUT", url, + headers=headers, + stream=stream, + timeout=timeout, + body=body, fields=fields) + + def patch(self, url, headers=None, + body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: + return self.request("PATCH", url, + headers=headers, + stream=stream, + timeout=timeout, + body=body, fields=fields) diff --git a/comfy/api/schemas/__init__.py b/comfy/api/schemas/__init__.py new file mode 100644 index 000000000..1ea0c6bd4 --- /dev/null +++ b/comfy/api/schemas/__init__.py @@ -0,0 +1,148 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import typing + +import typing_extensions + +from .schema import ( + get_class, + none_type_, + classproperty, + Bool, + FileIO, + Schema, + SingletonMeta, + AnyTypeSchema, + UnsetAnyTypeSchema, + INPUT_TYPES_ALL +) + +from .schemas import ( + ListSchema, + NoneSchema, + NumberSchema, + IntSchema, + Int32Schema, + Int64Schema, + Float32Schema, + Float64Schema, + StrSchema, + UUIDSchema, + DateSchema, + DateTimeSchema, + DecimalSchema, + BytesSchema, + FileSchema, + BinarySchema, + BoolSchema, + NotAnyTypeSchema, + OUTPUT_BASE_TYPES, + DictSchema +) +from .validation import ( + PatternInfo, + ValidationMetadata, + immutabledict +) +from .format import ( + as_date, + as_datetime, + as_decimal, + as_uuid +) + +def typed_dict_to_instance(t_dict: typing_extensions._TypedDictMeta) -> typing.Mapping: # type: ignore + res = {} + for key, val in t_dict.__annotations__.items(): + if isinstance(val, typing._GenericAlias): # type: ignore + # typing.Type[W] -> W + val_cls = typing.get_args(val)[0] + res[key] = val_cls + return res + +X = typing.TypeVar('X', bound=typing.Tuple) + +def tuple_to_instance(tup: typing.Type[X]) -> X: + res = [] + for arg in typing.get_args(tup): + if isinstance(arg, typing._GenericAlias): # type: ignore + # typing.Type[Schema] -> Schema + arg_cls = typing.get_args(arg)[0] + res.append(arg_cls) + return tuple(res) # type: ignore + + +class Unset: + """ + An instance of this class is set as the default value for object type(dict) properties that are optional + When a property has an unset value, that property will not be assigned in the dict + """ + pass + +unset: Unset = Unset() + +def key_unknown_error_msg(key: str) -> str: + return (f"Invalid key. The key {key} is not a known key in this payload. " + "If this key is an additional property, use get_additional_property_" + ) + +def raise_if_key_known( + key: str, + required_keys: typing.FrozenSet[str], + optional_keys: typing.FrozenSet[str] +): + if key in required_keys or key in optional_keys: + raise ValueError(f"The key {key} is a known property, use get_property to access its value") + +__all__ = [ + 'get_class', + 'none_type_', + 'classproperty', + 'Bool', + 'FileIO', + 'Schema', + 'SingletonMeta', + 'AnyTypeSchema', + 'UnsetAnyTypeSchema', + 'INPUT_TYPES_ALL', + 'ListSchema', + 'NoneSchema', + 'NumberSchema', + 'IntSchema', + 'Int32Schema', + 'Int64Schema', + 'Float32Schema', + 'Float64Schema', + 'StrSchema', + 'UUIDSchema', + 'DateSchema', + 'DateTimeSchema', + 'DecimalSchema', + 'BytesSchema', + 'FileSchema', + 'BinarySchema', + 'BoolSchema', + 'NotAnyTypeSchema', + 'OUTPUT_BASE_TYPES', + 'DictSchema', + 'PatternInfo', + 'ValidationMetadata', + 'immutabledict', + 'as_date', + 'as_datetime', + 'as_decimal', + 'as_uuid', + 'typed_dict_to_instance', + 'tuple_to_instance', + 'Unset', + 'unset', + 'key_unknown_error_msg', + 'raise_if_key_known' +] \ No newline at end of file diff --git a/comfy/api/schemas/format.py b/comfy/api/schemas/format.py new file mode 100644 index 000000000..bb614903b --- /dev/null +++ b/comfy/api/schemas/format.py @@ -0,0 +1,115 @@ +import datetime +import decimal +import functools +import typing +import uuid + +from dateutil import parser, tz + + +class CustomIsoparser(parser.isoparser): + def __init__(self, sep: typing.Optional[str] = None): + """ + :param sep: + A single character that separates date and time portions. If + ``None``, the parser will accept any single character. + For strict ISO-8601 adherence, pass ``'T'``. + """ + if sep is not None: + if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): + raise ValueError('Separator must be a single, non-numeric ' + + 'ASCII character') + + used_sep = sep.encode('ascii') + else: + used_sep = None + + self._sep = used_sep + + @staticmethod + def __get_ascii_bytes(str_in: str) -> bytes: + # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII + # ASCII is the same in UTF-8 + try: + return str_in.encode('ascii') + except UnicodeEncodeError as e: + msg = 'ISO-8601 strings should contain only ASCII characters' + raise ValueError(msg) from e + + def __parse_isodate(self, dt_str: str) -> typing.Tuple[typing.Tuple[int, int, int], int]: + dt_str_ascii = self.__get_ascii_bytes(dt_str) + values = self._parse_isodate(dt_str_ascii) # type: ignore + values = typing.cast(typing.Tuple[typing.List[int], int], values) + components = typing.cast( typing.Tuple[int, int, int], tuple(values[0])) + pos = values[1] + return components, pos + + def __parse_isotime(self, dt_str: str) -> typing.Tuple[int, int, int, int, typing.Optional[typing.Union[tz.tzutc, tz.tzoffset]]]: + dt_str_ascii = self.__get_ascii_bytes(dt_str) + values = self._parse_isotime(dt_str_ascii) # type: ignore + components: typing.Tuple[int, int, int, int, typing.Optional[typing.Union[tz.tzutc, tz.tzoffset]]] = tuple(values) # type: ignore + return components + + def parse_isodatetime(self, dt_str: str) -> datetime.datetime: + date_components, pos = self.__parse_isodate(dt_str) + if len(dt_str) <= pos: + # len(components) <= 3 + raise ValueError('Value is not a datetime') + if self._sep is None or dt_str[pos:pos + 1] == self._sep: + hour, minute, second, microsecond, tzinfo = self.__parse_isotime(dt_str[pos + 1:]) + if hour == 24: + hour = 0 + components = (*date_components, hour, minute, second, microsecond, tzinfo) + return datetime.datetime(*components) + datetime.timedelta(days=1) + else: + components = (*date_components, hour, minute, second, microsecond, tzinfo) + else: + raise ValueError('String contains unknown ISO components') + + return datetime.datetime(*components) + + def parse_isodate_str(self, datestr: str) -> datetime.date: + components, pos = self.__parse_isodate(datestr) + + if len(datestr) > pos: + raise ValueError('String contains invalid time components') + + if len(components) > 3: + raise ValueError('String contains invalid time components') + + return datetime.date(*components) + +DEFAULT_ISOPARSER = CustomIsoparser() + +@functools.lru_cache() +def as_date(arg: str) -> datetime.date: + """ + type = "string" + format = "date" + """ + return DEFAULT_ISOPARSER.parse_isodate_str(arg) + +@functools.lru_cache() +def as_datetime(arg: str) -> datetime.datetime: + """ + type = "string" + format = "date-time" + """ + return DEFAULT_ISOPARSER.parse_isodatetime(arg) + +@functools.lru_cache() +def as_decimal(arg: str) -> decimal.Decimal: + """ + Applicable when storing decimals that are sent over the wire as strings + type = "string" + format = "number" + """ + return decimal.Decimal(arg) + +@functools.lru_cache() +def as_uuid(arg: str) -> uuid.UUID: + """ + type = "string" + format = "uuid" + """ + return uuid.UUID(arg) \ No newline at end of file diff --git a/comfy/api/schemas/original_immutabledict.py b/comfy/api/schemas/original_immutabledict.py new file mode 100644 index 000000000..3897e140a --- /dev/null +++ b/comfy/api/schemas/original_immutabledict.py @@ -0,0 +1,97 @@ +""" +MIT License + +Copyright (c) 2020 Corentin Garcia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +from __future__ import annotations +import typing +import typing_extensions + +_K = typing.TypeVar("_K") +_V = typing.TypeVar("_V", covariant=True) + + +class immutabledict(typing.Mapping[_K, _V]): + """ + An immutable wrapper around dictionaries that implements + the complete :py:class:`collections.Mapping` interface. + It can be used as a drop-in replacement for dictionaries + where immutability is desired. + + Note: custom version of this class made to remove __init__ + """ + + dict_cls: typing.Type[typing.Dict[typing.Any, typing.Any]] = dict + _dict: typing.Dict[_K, _V] + _hash: typing.Optional[int] + + @classmethod + def fromkeys( + cls, seq: typing.Iterable[_K], value: typing.Optional[_V] = None + ) -> "immutabledict[_K, _V]": + return cls(dict.fromkeys(seq, value)) + + def __new__(cls, *args: typing.Any) -> typing_extensions.Self: + inst = super().__new__(cls) + setattr(inst, '_dict', cls.dict_cls(*args)) + setattr(inst, '_hash', None) + return inst + + def __getitem__(self, key: _K) -> _V: + return self._dict[key] + + def __contains__(self, key: object) -> bool: + return key in self._dict + + def __iter__(self) -> typing.Iterator[_K]: + return iter(self._dict) + + def __len__(self) -> int: + return len(self._dict) + + def __repr__(self) -> str: + return "%s(%r)" % (self.__class__.__name__, self._dict) + + def __hash__(self) -> int: + if self._hash is None: + h = 0 + for key, value in self.items(): + h ^= hash((key, value)) + self._hash = h + + return self._hash + + def __or__(self, other: typing.Any) -> immutabledict[_K, _V]: + if not isinstance(other, (dict, self.__class__)): + return NotImplemented + new = dict(self) + new.update(other) + return self.__class__(new) + + def __ror__(self, other: typing.Any) -> typing.Dict[typing.Any, typing.Any]: + if not isinstance(other, (dict, self.__class__)): + return NotImplemented + new = dict(other) + new.update(self) + return new + + def __ior__(self, other: typing.Any) -> immutabledict[_K, _V]: + raise TypeError(f"'{self.__class__.__name__}' object is not mutable") diff --git a/comfy/api/schemas/schema.py b/comfy/api/schemas/schema.py new file mode 100644 index 000000000..1118f40e1 --- /dev/null +++ b/comfy/api/schemas/schema.py @@ -0,0 +1,729 @@ +from __future__ import annotations +import datetime +import dataclasses +import io +import types +import typing +import uuid + +import functools +import typing_extensions + +from comfy.api import exceptions +from comfy.api.configurations import schema_configuration + +from . import validation + +_T_co = typing.TypeVar("_T_co", covariant=True) + + +class SequenceNotStr(typing.Protocol[_T_co]): + """ + if a Protocol would define the interface of Sequence, this protocol + would NOT allow str/bytes as their __contains__ methods are incompatible with the definition in Sequence + methods from: https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection + """ + def __contains__(self, value: object, /) -> bool: + raise NotImplementedError + + def __getitem__(self, index, /): + raise NotImplementedError + + def __len__(self) -> int: + raise NotImplementedError + + def __iter__(self) -> typing.Iterator[_T_co]: + raise NotImplementedError + + def __reversed__(self, /) -> typing.Iterator[_T_co]: + raise NotImplementedError + +none_type_ = type(None) +T = typing.TypeVar('T', bound=typing.Mapping) +U = typing.TypeVar('U', bound=SequenceNotStr) +W = typing.TypeVar('W') + + +class SchemaTyped: + additional_properties: typing.Type[Schema] + all_of: typing.Tuple[typing.Type[Schema], ...] + any_of: typing.Tuple[typing.Type[Schema], ...] + discriminator: typing.Mapping[str, typing.Mapping[str, typing.Type[Schema]]] + default: typing.Union[str, int, float, bool, None] + enum_value_to_name: typing.Mapping[typing.Union[int, float, str, Bool, None], str] + exclusive_maximum: typing.Union[int, float] + exclusive_minimum: typing.Union[int, float] + format: str + inclusive_maximum: typing.Union[int, float] + inclusive_minimum: typing.Union[int, float] + items: typing.Type[Schema] + max_items: int + max_length: int + max_properties: int + min_items: int + min_length: int + min_properties: int + multiple_of: typing.Union[int, float] + not_: typing.Type[Schema] + one_of: typing.Tuple[typing.Type[Schema], ...] + pattern: validation.PatternInfo + properties: typing.Mapping[str, typing.Type[Schema]] + required: typing.FrozenSet[str] + types: typing.FrozenSet[typing.Type] + unique_items: bool + + +class FileIO(io.FileIO): + """ + A class for storing files + Note: this class is not immutable + """ + + def __new__(cls, arg: typing.Union[io.FileIO, io.BufferedReader]): + if isinstance(arg, (io.FileIO, io.BufferedReader)): + if arg.closed: + raise exceptions.ApiValueError('Invalid file state; file is closed and must be open') + arg.close() + inst = super(FileIO, cls).__new__(cls, arg.name) # type: ignore + super(FileIO, inst).__init__(arg.name) + return inst + raise exceptions.ApiValueError('FileIO must be passed arg which contains the open file') + + def __init__(self, arg: typing.Union[io.FileIO, io.BufferedReader]): + """ + Needed for instantiation when passing in arguments of the above type + """ + pass + + +class classproperty(typing.Generic[W]): + def __init__(self, method: typing.Callable[..., W]): + self.__method = method + functools.update_wrapper(self, method) # type: ignore + + def __get__(self, obj, cls=None) -> W: + if cls is None: + cls = type(obj) + return self.__method(cls) + + +class Bool: + _instances: typing.Dict[typing.Tuple[type, bool], Bool] = {} + """ + This class is needed to replace bool during validation processing + json schema requires that 0 != False and 1 != True + python implementation defines 0 == False and 1 == True + To meet the json schema requirements, all bool instances are replaced with Bool singletons + during validation only, and then bool values are returned from validation + """ + + def __new__(cls, arg_: bool, **kwargs): + """ + Method that implements singleton + cls base classes: BoolClass, NoneClass, str, decimal.Decimal + The 3rd key is used in the tuple below for a corner case where an enum contains integer 1 + However 1.0 can also be ingested into that enum schema because 1.0 == 1 and + Decimal('1.0') == Decimal('1') + But if we omitted the 3rd value in the key, then Decimal('1.0') would be stored as Decimal('1') + and json serializing that instance would be '1' rather than the expected '1.0' + Adding the 3rd value, the str of arg_ ensures that 1.0 -> Decimal('1.0') which is serialized as 1.0 + """ + key = (cls, arg_) + if key not in cls._instances: + inst = super().__new__(cls) + cls._instances[key] = inst + return cls._instances[key] + + def __repr__(self): + if bool(self): + return f'' + return f'' + + @classproperty + def TRUE(cls): + return cls(True) # type: ignore + + @classproperty + def FALSE(cls): + return cls(False) # type: ignore + + @functools.lru_cache() + def __bool__(self) -> bool: + for key, instance in self._instances.items(): + if self is instance: + return bool(key[1]) + raise ValueError('Unable to find the boolean value of this instance') + + +def cast_to_allowed_types( + arg: typing.Union[ + dict, + validation.immutabledict, + list, + tuple, + float, + int, + str, + datetime.date, + datetime.datetime, + uuid.UUID, + bool, + None, + bytes, + io.FileIO, + io.BufferedReader, + ], + from_server: bool, + validated_path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Set[typing.Union[str, int, float, bool, None, validation.immutabledict, tuple]]], + path_to_item: typing.Tuple[typing.Union[str, int], ...], + path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], type] +) -> typing.Union[ + validation.immutabledict, + tuple, + float, + int, + str, + bytes, + Bool, + None, + FileIO +]: + """ + Casts the input payload arg into the allowed types + The input validated_path_to_schemas is mutated by running this function + + When from_server is False then + - date/datetime is cast to str + - int/float is cast to Decimal + + If a Schema instance is passed in it is converted back to a primitive instance because + One may need to validate that data to the original Schema class AND additional different classes + those additional classes will need to be added to the new manufactured class for that payload + If the code didn't do this and kept the payload as a Schema instance it would fail to validate to other + Schema classes and the code wouldn't be able to mfg a new class that includes all valid schemas + TODO: store the validated schema classes in validation_metadata + + Args: + arg: the payload + from_server: whether this payload came from the server or not + validated_path_to_schemas: a dict that stores the validated classes at any path location in the payload + """ + type_error = exceptions.ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}") + if isinstance(arg, str): + path_to_type[path_to_item] = str + return str(arg) + elif isinstance(arg, (dict, validation.immutabledict)): + path_to_type[path_to_item] = validation.immutabledict + return validation.immutabledict( + { + key: cast_to_allowed_types( + val, + from_server, + validated_path_to_schemas, + path_to_item + (key,), + path_to_type, + ) + for key, val in arg.items() + } + ) + elif isinstance(arg, bool): + """ + this check must come before isinstance(arg, (int, float)) + because isinstance(True, int) is True + """ + path_to_type[path_to_item] = Bool + if arg: + return Bool.TRUE + return Bool.FALSE + elif isinstance(arg, int): + path_to_type[path_to_item] = int + return arg + elif isinstance(arg, float): + path_to_type[path_to_item] = float + return arg + elif isinstance(arg, (tuple, list)): + path_to_type[path_to_item] = tuple + return tuple( + [ + cast_to_allowed_types( + item, + from_server, + validated_path_to_schemas, + path_to_item + (i,), + path_to_type, + ) + for i, item in enumerate(arg) + ] + ) + elif arg is None: + path_to_type[path_to_item] = type(None) + return None + elif isinstance(arg, (datetime.date, datetime.datetime)): + path_to_type[path_to_item] = str + if not from_server: + return arg.isoformat() + raise type_error + elif isinstance(arg, uuid.UUID): + path_to_type[path_to_item] = str + if not from_server: + return str(arg) + raise type_error + elif isinstance(arg, bytes): + path_to_type[path_to_item] = bytes + return bytes(arg) + elif isinstance(arg, (io.FileIO, io.BufferedReader)): + path_to_type[path_to_item] = FileIO + return FileIO(arg) + raise exceptions.ApiTypeError('Invalid type passed in got input={} type={}'.format(arg, type(arg))) + + +class SingletonMeta(type): + """ + A singleton class for schemas + Schemas are frozen classes that are never instantiated with init args + All args come from defaults + """ + _instances: typing.Dict[type, typing.Any] = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + + +class Schema(typing.Generic[T, U], validation.SchemaValidator, metaclass=SingletonMeta): + + @classmethod + def __get_path_to_schemas( + cls, + arg, + validation_metadata: validation.ValidationMetadata, + path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type] + ) -> typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]]: + """ + Run all validations in the json schema and return a dict of + json schema to tuple of validated schemas + """ + _path_to_schemas: validation.PathToSchemasType = {} + if validation_metadata.validation_ran_earlier(cls): + validation.add_deeper_validated_schemas(validation_metadata, _path_to_schemas) + else: + other_path_to_schemas = cls._validate(arg, validation_metadata=validation_metadata) + validation.update(_path_to_schemas, other_path_to_schemas) + # loop through it make a new class for each entry + # do not modify the returned result because it is cached and we would be modifying the cached value + path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]] = {} + for path, schema_classes in _path_to_schemas.items(): + schema = typing.cast(typing.Type[Schema], tuple(schema_classes)[-1]) + path_to_schemas[path] = schema + """ + For locations that validation did not check + the code still needs to store type + schema information for instantiation + All of those schemas will be UnsetAnyTypeSchema + """ + missing_paths = path_to_type.keys() - path_to_schemas.keys() + for missing_path in missing_paths: + path_to_schemas[missing_path] = UnsetAnyTypeSchema + + return path_to_schemas + + @staticmethod + def __get_items( + arg: tuple, + path_to_item: typing.Tuple[typing.Union[str, int], ...], + path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]] + ): + ''' + Schema __get_items + ''' + cast_items = [] + + for i, value in enumerate(arg): + item_path_to_item = path_to_item + (i,) + item_cls = path_to_schemas[item_path_to_item] + new_value = item_cls._get_new_instance_without_conversion( + value, + item_path_to_item, + path_to_schemas + ) + cast_items.append(new_value) + + return tuple(cast_items) + + @staticmethod + def __get_properties( + arg: validation.immutabledict[str, typing.Any], + path_to_item: typing.Tuple[typing.Union[str, int], ...], + path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]] + ): + """ + Schema __get_properties, this is how properties are set + These values already passed validation + """ + dict_items = {} + + for property_name_js, value in arg.items(): + property_path_to_item = path_to_item + (property_name_js,) + property_cls = path_to_schemas[property_path_to_item] + new_value = property_cls._get_new_instance_without_conversion( + value, + property_path_to_item, + path_to_schemas + ) + dict_items[property_name_js] = new_value + + return validation.immutabledict(dict_items) + + @classmethod + def _get_new_instance_without_conversion( + cls, + arg: typing.Union[int, float, None, Bool, str, validation.immutabledict, tuple, FileIO, bytes], + path_to_item: typing.Tuple[typing.Union[str, int], ...], + path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]] + ): + # We have a Dynamic class and we are making an instance of it + if isinstance(arg, validation.immutabledict): + used_arg = cls.__get_properties(arg, path_to_item, path_to_schemas) + elif isinstance(arg, tuple): + used_arg = cls.__get_items(arg, path_to_item, path_to_schemas) + elif isinstance(arg, Bool): + return bool(arg) + else: + """ + str, int, float, FileIO, bytes + FileIO = openapi binary type and the user inputs a file + bytes = openapi binary type and the user inputs bytes + """ + return arg + arg_type = type(arg) + type_to_output_cls = cls.__get_type_to_output_cls() + if type_to_output_cls is None: + return used_arg + if arg_type not in type_to_output_cls: + return used_arg + output_cls = type_to_output_cls[arg_type] + if arg_type is tuple: + inst = super(output_cls, output_cls).__new__(output_cls, used_arg) # type: ignore + inst = typing.cast(U, inst) + return inst + assert issubclass(output_cls, validation.immutabledict) + inst = super(output_cls, output_cls).__new__(output_cls, used_arg) # type: ignore + inst = typing.cast(T, inst) + return inst + + @typing.overload + @classmethod + def validate_base( + cls, + arg: None, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> None: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Literal[True], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[True]: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Literal[False], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[False]: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: bool, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bool: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: int, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> int: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: float, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> float: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Union[str, datetime.date, datetime.datetime, uuid.UUID], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: SequenceNotStr[INPUT_TYPES_ALL], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> U: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: U, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> U: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Mapping[str, object], # object needed as value type for typeddict inputs + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> T: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Union[ + typing.Mapping[str, INPUT_TYPES_ALL], + T + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> T: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: typing.Union[io.FileIO, io.BufferedReader], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> FileIO: ... + + @typing.overload + @classmethod + def validate_base( + cls, + arg: bytes, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bytes: ... + + @classmethod + def validate_base( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + """ + Schema validate_base + + Args: + arg (int/float/str/list/tuple/dict/validation.immutabledict/bool/None): the value + configuration: contains the schema_configuration.SchemaConfiguration that enables json schema validation keywords + like minItems, minLength etc + """ + if isinstance(arg, (tuple, validation.immutabledict)): + type_to_output_cls = cls.__get_type_to_output_cls() + if type_to_output_cls is not None: + for output_cls in type_to_output_cls.values(): + if isinstance(arg, output_cls): + # U + T use case, don't run validations twice + return arg + + from_server = False + validated_path_to_schemas: typing.Dict[ + typing.Tuple[typing.Union[str, int], ...], + typing.Set[typing.Union[str, int, float, bool, None, validation.immutabledict, tuple]] + ] = {} + path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], type] = {} + cast_arg = cast_to_allowed_types( + arg, from_server, validated_path_to_schemas, ('args[0]',), path_to_type) + validation_metadata = validation.ValidationMetadata( + path_to_item=('args[0]',), + configuration=configuration or schema_configuration.SchemaConfiguration(), + validated_path_to_schemas=validation.immutabledict(validated_path_to_schemas) + ) + path_to_schemas = cls.__get_path_to_schemas(cast_arg, validation_metadata, path_to_type) + return cls._get_new_instance_without_conversion( + cast_arg, + validation_metadata.path_to_item, + path_to_schemas, + ) + + @classmethod + def __get_type_to_output_cls(cls) -> typing.Optional[typing.Mapping[type, type]]: + type_to_output_cls = getattr(cls(), 'type_to_output_cls', None) + type_to_output_cls = typing.cast(typing.Optional[typing.Mapping[type, type]], type_to_output_cls) + return type_to_output_cls + + +def get_class( + item_cls: typing.Union[types.FunctionType, staticmethod, typing.Type[Schema]], + local_namespace: typing.Optional[dict] = None +) -> typing.Type[Schema]: + if isinstance(item_cls, typing._GenericAlias): # type: ignore + # petstore_api.schemas.StrSchema[~U] -> petstore_api.schemas.StrSchema + origin_cls = typing.get_origin(item_cls) + if origin_cls is None: + raise ValueError('origin class must not be None') + return origin_cls + elif isinstance(item_cls, types.FunctionType): + # referenced schema + return item_cls() + elif isinstance(item_cls, staticmethod): + # referenced schema + return item_cls.__func__() + elif isinstance(item_cls, type): + return item_cls + elif isinstance(item_cls, typing.ForwardRef): + return item_cls._evaluate(None, local_namespace) + raise ValueError('invalid class value passed in') + + +@dataclasses.dataclass(frozen=True) +class AnyTypeSchema(Schema[T, U]): + # Python representation of a schema defined as true or {} + + @typing.overload + @classmethod + def validate( + cls, + arg: None, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> None: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal[True], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[True]: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal[False], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[False]: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: bool, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bool: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: int, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> int: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: float, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> float: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Union[str, datetime.date, datetime.datetime, uuid.UUID], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: SequenceNotStr[INPUT_TYPES_ALL], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> U: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: U, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> U: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Union[ + typing.Mapping[str, INPUT_TYPES_ALL], + T + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> T: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Union[io.FileIO, io.BufferedReader], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> FileIO: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: bytes, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bytes: ... + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + return cls.validate_base( + arg, + configuration=configuration + ) + +class UnsetAnyTypeSchema(AnyTypeSchema[T, U]): + # Used when additionalProperties/items was not explicitly defined and a defining schema is needed + pass + +INPUT_TYPES_ALL = typing.Union[ + dict, + validation.immutabledict, + typing.Mapping[str, object], # for TypedDict + list, + tuple, + float, + int, + str, + datetime.date, + datetime.datetime, + uuid.UUID, + bool, + None, + bytes, + io.FileIO, + io.BufferedReader, + FileIO +] \ No newline at end of file diff --git a/comfy/api/schemas/schemas.py b/comfy/api/schemas/schemas.py new file mode 100644 index 000000000..9dc95514f --- /dev/null +++ b/comfy/api/schemas/schemas.py @@ -0,0 +1,375 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +import datetime +import dataclasses +import io +import typing +import uuid + +import typing_extensions + +from comfy.api.configurations import schema_configuration + +from . import schema, validation + + +@dataclasses.dataclass(frozen=True) +class ListSchema(schema.Schema[validation.immutabledict, tuple]): + types: typing.FrozenSet[typing.Type] = frozenset({tuple}) + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Union[ + typing.List[schema.INPUT_TYPES_ALL], + schema.U + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> schema.U: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Union[ + typing.Tuple[schema.INPUT_TYPES_ALL, ...], + schema.U + ], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> schema.U: ... + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ): + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class NoneSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({type(None)}) + + @classmethod + def validate( + cls, + arg: None, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> None: + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class NumberSchema(schema.Schema): + """ + This is used for type: number with no format + Both integers AND floats are accepted + """ + types: typing.FrozenSet[typing.Type] = frozenset({float, int}) + + @typing.overload + @classmethod + def validate( + cls, + arg: int, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> int: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: float, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> float: ... + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ): + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class IntSchema(NumberSchema): + types: typing.FrozenSet[typing.Type] = frozenset({int, float}) + format: str = 'int' + + +@dataclasses.dataclass(frozen=True) +class Int32Schema(IntSchema): + types: typing.FrozenSet[typing.Type] = frozenset({int, float}) + format: str = 'int32' + + +@dataclasses.dataclass(frozen=True) +class Int64Schema(IntSchema): + types: typing.FrozenSet[typing.Type] = frozenset({int, float}) + format: str = 'int64' + + +@dataclasses.dataclass(frozen=True) +class Float32Schema(NumberSchema): + types: typing.FrozenSet[typing.Type] = frozenset({float}) + format: str = 'float' + + +@dataclasses.dataclass(frozen=True) +class Float64Schema(NumberSchema): + types: typing.FrozenSet[typing.Type] = frozenset({float}) + format: str = 'double' + + +@dataclasses.dataclass(frozen=True) +class StrSchema(schema.Schema): + """ + date + datetime string types must inherit from this class + That is because one can validate a str payload as both: + - type: string (format unset) + - type: string, format: date + """ + types: typing.FrozenSet[typing.Type] = frozenset({str}) + + @classmethod + def validate( + cls, + arg: str, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class UUIDSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({str}) + format: str = 'uuid' + + @classmethod + def validate( + cls, + arg: typing.Union[str, uuid.UUID], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class DateSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({str}) + format: str = 'date' + + @classmethod + def validate( + cls, + arg: typing.Union[str, datetime.date], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class DateTimeSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({str}) + format: str = 'date-time' + + @classmethod + def validate( + cls, + arg: typing.Union[str, datetime.datetime], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class DecimalSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({str}) + format: str = 'number' + + @classmethod + def validate( + cls, + arg: str, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> str: + """ + Note: Decimals may not be passed in because cast_to_allowed_types is only invoked once for payloads + which can be simple (str) or complex (dicts or lists with nested values) + Because casting is only done once and recursively casts all values prior to validation then for a potential + client side Decimal input if Decimal was accepted as an input in DecimalSchema then one would not know + if one was using it for a StrSchema (where it should be cast to str) or one is using it for NumberSchema + where it should stay as Decimal. + """ + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class BytesSchema(schema.Schema): + """ + this class will subclass bytes and is immutable + """ + types: typing.FrozenSet[typing.Type] = frozenset({bytes}) + + @classmethod + def validate( + cls, + arg: bytes, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bytes: + return cls.validate_base(arg) + + +@dataclasses.dataclass(frozen=True) +class FileSchema(schema.Schema): + """ + This class is NOT immutable + Dynamic classes are built using it for example when AnyType allows in binary data + Al other schema classes ARE immutable + If one wanted to make this immutable one could make this a DictSchema with required properties: + - data = BytesSchema (which would be an immutable bytes based schema) + - file_name = StrSchema + and cast_to_allowed_types would convert bytes and file instances into dicts containing data + file_name + The downside would be that data would be stored in memory which one may not want to do for very large files + + The developer is responsible for closing this file and deleting it + + This class was kept as mutable: + - to allow file reading and writing to disk + - to be able to preserve file name info + """ + types: typing.FrozenSet[typing.Type] = frozenset({schema.FileIO}) + + @classmethod + def validate( + cls, + arg: typing.Union[io.FileIO, io.BufferedReader], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> schema.FileIO: + return cls.validate_base(arg) + + +@dataclasses.dataclass(frozen=True) +class BinarySchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({schema.FileIO, bytes}) + format: str = 'binary' + + one_of: typing.Tuple[typing.Type[schema.Schema], ...] = ( + BytesSchema, + FileSchema, + ) + + @classmethod + def validate( + cls, + arg: typing.Union[io.FileIO, io.BufferedReader, bytes], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Union[schema.FileIO, bytes]: + return cls.validate_base(arg) + + +@dataclasses.dataclass(frozen=True) +class BoolSchema(schema.Schema): + types: typing.FrozenSet[typing.Type] = frozenset({schema.Bool}) + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal[True], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[True]: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Literal[False], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> typing.Literal[False]: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: bool, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> bool: ... + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ): + return super().validate_base(arg, configuration=configuration) + + +@dataclasses.dataclass(frozen=True) +class NotAnyTypeSchema(schema.AnyTypeSchema): + """ + Python representation of a schema defined as false or {'not': {}} + Does not allow inputs in of AnyType + Note: validations on this class are never run because the code knows that no inputs will ever validate + """ + not_: typing.Type[schema.Schema] = schema.AnyTypeSchema + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ): + return super().validate_base(arg, configuration=configuration) + +OUTPUT_BASE_TYPES = typing.Union[ + validation.immutabledict[str, 'OUTPUT_BASE_TYPES'], + str, + int, + float, + bool, + schema.none_type_, + typing.Tuple['OUTPUT_BASE_TYPES', ...], + bytes, + schema.FileIO +] + + +@dataclasses.dataclass(frozen=True) +class DictSchema(schema.Schema[schema.validation.immutabledict[str, OUTPUT_BASE_TYPES], tuple]): + types: typing.FrozenSet[typing.Type] = frozenset({validation.immutabledict}) + + @typing.overload + @classmethod + def validate( + cls, + arg: schema.validation.immutabledict[str, OUTPUT_BASE_TYPES], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]: ... + + @typing.overload + @classmethod + def validate( + cls, + arg: typing.Mapping[str, schema.INPUT_TYPES_ALL], + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None + ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]: ... + + @classmethod + def validate( + cls, + arg, + configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None, + ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]: + return super().validate_base(arg, configuration=configuration) diff --git a/comfy/api/schemas/validation.py b/comfy/api/schemas/validation.py new file mode 100644 index 000000000..33de2b990 --- /dev/null +++ b/comfy/api/schemas/validation.py @@ -0,0 +1,1446 @@ +# coding: utf-8 + +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +import collections +import dataclasses +import decimal +import re +import sys +import types +import typing +import uuid + +import typing_extensions + +from comfy.api import exceptions +from comfy.api.configurations import schema_configuration + +from . import format, original_immutabledict + +immutabledict = original_immutabledict.immutabledict + + +@dataclasses.dataclass +class ValidationMetadata: + """ + A class storing metadata that is needed to validate OpenApi Schema payloads + """ + path_to_item: typing.Tuple[typing.Union[str, int], ...] + configuration: schema_configuration.SchemaConfiguration + validated_path_to_schemas: typing.Mapping[ + typing.Tuple[typing.Union[str, int], ...], + typing.Mapping[type, None] + ] = dataclasses.field(default_factory=dict) + seen_classes: typing.FrozenSet[type] = frozenset() + + def validation_ran_earlier(self, cls: type) -> bool: + validated_schemas: typing.Union[typing.Mapping[type, None], None] = self.validated_path_to_schemas.get(self.path_to_item) + if validated_schemas and cls in validated_schemas: + return True + if cls in self.seen_classes: + return True + return False + +def _raise_validation_error_message(value, constraint_msg, constraint_value, path_to_item, additional_txt=""): + raise exceptions.ApiValueError( + "Invalid value `{value}`, {constraint_msg} `{constraint_value}`{additional_txt} at {path_to_item}".format( + value=value, + constraint_msg=constraint_msg, + constraint_value=constraint_value, + additional_txt=additional_txt, + path_to_item=path_to_item, + ) + ) + + +class SchemaValidator: + __excluded_cls_properties = { + '__module__', + '__dict__', + '__weakref__', + '__doc__', + '__annotations__', + 'default', # excluded because it has no impact on validation + 'type_to_output_cls', # used to pluck the output class for instantiation + } + + @classmethod + def _validate( + cls, + arg, + validation_metadata: ValidationMetadata, + ) -> PathToSchemasType: + """ + SchemaValidator validate + All keyword validation except for type checking was done in calling stack frames + If those validations passed, the validated classes are collected in path_to_schemas + """ + cls_schema = cls() + json_schema_data = { + k: v + for k, v in vars(cls_schema).items() + if k not in cls.__excluded_cls_properties + and k + not in validation_metadata.configuration.disabled_json_schema_python_keywords + } + contains_path_to_schemas = [] + path_to_schemas: PathToSchemasType = {} + if 'contains' in vars(cls_schema): + contains_path_to_schemas = _get_contains_path_to_schemas( + arg, + vars(cls_schema)['contains'], + validation_metadata, + path_to_schemas + ) + if_path_to_schemas = None + if 'if_' in vars(cls_schema): + if_path_to_schemas = _get_if_path_to_schemas( + arg, + vars(cls_schema)['if_'], + validation_metadata, + ) + validated_pattern_properties: typing.Optional[PathToSchemasType] = None + if 'pattern_properties' in vars(cls_schema): + validated_pattern_properties = _get_validated_pattern_properties( + arg, + vars(cls_schema)['pattern_properties'], + cls, + validation_metadata + ) + prefix_items_length = 0 + if 'prefix_items' in vars(cls_schema): + prefix_items_length = len(vars(cls_schema)['prefix_items']) + for keyword, val in json_schema_data.items(): + used_val: typing.Any + if keyword in {'contains', 'min_contains', 'max_contains'}: + used_val = (val, contains_path_to_schemas) + elif keyword == 'items': + used_val = (val, prefix_items_length) + elif keyword in {'unevaluated_items', 'unevaluated_properties'}: + used_val = (val, path_to_schemas) + elif keyword in {'types'}: + format: typing.Optional[str] = vars(cls_schema).get('format', None) + used_val = (val, format) + elif keyword in {'pattern_properties', 'additional_properties'}: + used_val = (val, validated_pattern_properties) + elif keyword in {'if_', 'then', 'else_'}: + used_val = (val, if_path_to_schemas) + else: + used_val = val + validator = json_schema_keyword_to_validator[keyword] + + other_path_to_schemas = validator( + arg, + used_val, + cls, + validation_metadata, + ) + if other_path_to_schemas: + update(path_to_schemas, other_path_to_schemas) + + base_class = type(arg) + if validation_metadata.path_to_item not in path_to_schemas: + path_to_schemas[validation_metadata.path_to_item] = dict() + path_to_schemas[validation_metadata.path_to_item][base_class] = None + path_to_schemas[validation_metadata.path_to_item][cls] = None + return path_to_schemas + +PathToSchemasType = typing.Dict[ + typing.Tuple[typing.Union[str, int], ...], + typing.Dict[ + typing.Union[ + typing.Type[SchemaValidator], + typing.Type[str], + typing.Type[int], + typing.Type[float], + typing.Type[bool], + typing.Type[None], + typing.Type[immutabledict], + typing.Type[tuple] + ], + None + ] +] + +def _get_class( + item_cls: typing.Union[types.FunctionType, staticmethod, typing.Type[SchemaValidator]], + local_namespace: typing.Optional[dict] = None +) -> typing.Type[SchemaValidator]: + if isinstance(item_cls, typing._GenericAlias): # type: ignore + # petstore_api.schemas.StrSchema[~U] -> petstore_api.schemas.StrSchema + origin_cls = typing.get_origin(item_cls) + if origin_cls is None: + raise ValueError('origin class must not be None') + return origin_cls + elif isinstance(item_cls, types.FunctionType): + # referenced schema + return item_cls() + elif isinstance(item_cls, staticmethod): + # referenced schema + return item_cls.__func__() + elif isinstance(item_cls, type): + return item_cls + elif isinstance(item_cls, typing.ForwardRef): + if sys.version_info < (3, 9): + return item_cls._evaluate(None, local_namespace) + return item_cls._evaluate(None, local_namespace, set()) + raise ValueError('invalid class value passed in') + + +def update(d: dict, u: dict): + """ + Adds u to d + Where each dict is collections.defaultdict(dict) + """ + if not u: + return d + for k, v in u.items(): + if k not in d: + d[k] = v + else: + d[k].update(v) + + +def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict): + # this is called if validation_ran_earlier and current and deeper locations need to be added + current_path_to_item = validation_metadata.path_to_item + other_path_to_schemas = {} + for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items(): + if len(path_to_item) < len(current_path_to_item): + continue + path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item + if path_begins_with_current_path: + other_path_to_schemas[path_to_item] = schemas + update(path_to_schemas, other_path_to_schemas) + + +def __get_valid_classes_phrase(input_classes): + """Returns a string phrase describing what types are allowed""" + all_classes = list(input_classes) + all_classes = sorted(all_classes, key=lambda cls: cls.__name__) + all_class_names = [cls.__name__ for cls in all_classes] + if len(all_class_names) == 1: + return "is {0}".format(all_class_names[0]) + return "is one of [{0}]".format(", ".join(all_class_names)) + + +def __type_error_message( + var_value=None, var_name=None, valid_classes=None, key_type=None +): + """ + Keyword Args: + var_value (any): the variable which has the type_error + var_name (str): the name of the variable which has the typ error + valid_classes (tuple): the accepted classes for current_item's + value + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a tuple + """ + key_or_value = "value" + if key_type: + key_or_value = "key" + valid_classes_phrase = __get_valid_classes_phrase(valid_classes) + msg = "Invalid type. Required {0} type {1} and " "passed type was {2}".format( + key_or_value, + valid_classes_phrase, + type(var_value).__name__, + ) + return msg + + +def __get_type_error(var_value, path_to_item, valid_classes, key_type=False): + error_msg = __type_error_message( + var_name=path_to_item[-1], + var_value=var_value, + valid_classes=valid_classes, + key_type=key_type, + ) + return exceptions.ApiTypeError( + error_msg, + path_to_item=path_to_item, + valid_classes=valid_classes, + key_type=key_type, + ) + + +@dataclasses.dataclass(frozen=True) +class PatternInfo: + pattern: str + flags: typing.Optional[re.RegexFlag] = None + + +def validate_types( + arg: typing.Any, + allowed_types_format: typing.Tuple[typing.Set[typing.Type], typing.Optional[str]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + allowed_types = allowed_types_format[0] + if type(arg) not in allowed_types: + raise __get_type_error( + arg, + validation_metadata.path_to_item, + allowed_types, + key_type=False, + ) + if isinstance(arg, bool) or not isinstance(arg, (int, float)): + return None + format = allowed_types_format[1] + if format and format == 'int' and arg != int(arg): + # there is a json schema test where 1.0 validates as an integer + raise exceptions.ApiValueError( + "Invalid non-integer value '{}' for type {} at {}".format( + arg, format, validation_metadata.path_to_item + ) + ) + return None + + +def validate_enum( + arg: typing.Any, + enum_value_to_name: typing.Dict[typing.Any, str], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if arg not in enum_value_to_name: + raise exceptions.ApiValueError("Invalid value {} passed in to {}, allowed_values={}".format(arg, cls, enum_value_to_name.keys())) + return None + + +def validate_unique_items( + arg: typing.Any, + unique_items_value: bool, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not unique_items_value or not isinstance(arg, tuple): + return None + if len(arg) == len(set(arg)): + return None + _raise_validation_error_message( + value=arg, + constraint_msg="duplicate items were found, and the tuple must not contain duplicates because", + constraint_value='unique_items==True', + path_to_item=validation_metadata.path_to_item + ) + + +def validate_min_items( + arg: typing.Any, + min_items: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, tuple): + return None + if len(arg) < min_items: + _raise_validation_error_message( + value=arg, + constraint_msg="number of items must be greater than or equal to", + constraint_value=min_items, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_max_items( + arg: typing.Any, + max_items: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, tuple): + return None + if len(arg) > max_items: + _raise_validation_error_message( + value=arg, + constraint_msg="number of items must be less than or equal to", + constraint_value=max_items, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_min_properties( + arg: typing.Any, + min_properties: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, immutabledict): + return None + if len(arg) < min_properties: + _raise_validation_error_message( + value=arg, + constraint_msg="number of properties must be greater than or equal to", + constraint_value=min_properties, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_max_properties( + arg: typing.Any, + max_properties: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, immutabledict): + return None + if len(arg) > max_properties: + _raise_validation_error_message( + value=arg, + constraint_msg="number of properties must be less than or equal to", + constraint_value=max_properties, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_min_length( + arg: typing.Any, + min_length: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, str): + return None + if len(arg) < min_length: + _raise_validation_error_message( + value=arg, + constraint_msg="length must be greater than or equal to", + constraint_value=min_length, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_max_length( + arg: typing.Any, + max_length: int, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, str): + return None + if len(arg) > max_length: + _raise_validation_error_message( + value=arg, + constraint_msg="length must be less than or equal to", + constraint_value=max_length, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_inclusive_minimum( + arg: typing.Any, + inclusive_minimum: typing.Union[int, float], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, (int, float)): + return None + if arg < inclusive_minimum: + _raise_validation_error_message( + value=arg, + constraint_msg="must be a value greater than or equal to", + constraint_value=inclusive_minimum, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_exclusive_minimum( + arg: typing.Any, + exclusive_minimum: typing.Union[int, float], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, (int, float)): + return None + if arg <= exclusive_minimum: + _raise_validation_error_message( + value=arg, + constraint_msg="must be a value greater than", + constraint_value=exclusive_minimum, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_inclusive_maximum( + arg: typing.Any, + inclusive_maximum: typing.Union[int, float], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, (int, float)): + return None + if arg > inclusive_maximum: + _raise_validation_error_message( + value=arg, + constraint_msg="must be a value less than or equal to", + constraint_value=inclusive_maximum, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_exclusive_maximum( + arg: typing.Any, + exclusive_maximum: typing.Union[int, float], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, (int, float)): + return None + if arg >= exclusive_maximum: + _raise_validation_error_message( + value=arg, + constraint_msg="must be a value less than", + constraint_value=exclusive_maximum, + path_to_item=validation_metadata.path_to_item + ) + return None + +def validate_multiple_of( + arg: typing.Any, + multiple_of: typing.Union[int, float], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, (int, float)): + return None + if (not (float(arg) / multiple_of).is_integer()): + # Note 'multipleOf' will be as good as the floating point arithmetic. + _raise_validation_error_message( + value=arg, + constraint_msg="value must be a multiple of", + constraint_value=multiple_of, + path_to_item=validation_metadata.path_to_item + ) + return None + + +def validate_pattern( + arg: typing.Any, + pattern_info: PatternInfo, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, str): + return None + flags = pattern_info.flags if pattern_info.flags is not None else 0 + if not re.search(pattern_info.pattern, arg, flags=flags): + if flags != 0: + # Don't print the regex flags if the flags are not + # specified in the OAS document. + _raise_validation_error_message( + value=arg, + constraint_msg="must match regular expression", + constraint_value=pattern_info.pattern, + path_to_item=validation_metadata.path_to_item, + additional_txt=" with flags=`{}`".format(flags) + ) + _raise_validation_error_message( + value=arg, + constraint_msg="must match regular expression", + constraint_value=pattern_info.pattern, + path_to_item=validation_metadata.path_to_item + ) + return None + + +__int32_inclusive_minimum = -2147483648 +__int32_inclusive_maximum = 2147483647 +__int64_inclusive_minimum = -9223372036854775808 +__int64_inclusive_maximum = 9223372036854775807 +__float_inclusive_minimum = -3.4028234663852886e+38 +__float_inclusive_maximum = 3.4028234663852886e+38 +__double_inclusive_minimum = -1.7976931348623157E+308 +__double_inclusive_maximum = 1.7976931348623157E+308 + +def __validate_numeric_format( + arg: typing.Union[int, float], + format_value: str, + validation_metadata: ValidationMetadata +) -> None: + if format_value[:3] == 'int': + # there is a json schema test where 1.0 validates as an integer + if arg != int(arg): + raise exceptions.ApiValueError( + "Invalid non-integer value '{}' for type {} at {}".format( + arg, format, validation_metadata.path_to_item + ) + ) + if format_value == 'int32': + if not __int32_inclusive_minimum <= arg <= __int32_inclusive_maximum: + raise exceptions.ApiValueError( + "Invalid value '{}' for type int32 at {}".format(arg, validation_metadata.path_to_item) + ) + return None + elif format_value == 'int64': + if not __int64_inclusive_minimum <= arg <= __int64_inclusive_maximum: + raise exceptions.ApiValueError( + "Invalid value '{}' for type int64 at {}".format(arg, validation_metadata.path_to_item) + ) + return None + return None + elif format_value in {'float', 'double'}: + if format_value == 'float': + if not __float_inclusive_minimum <= arg <= __float_inclusive_maximum: + raise exceptions.ApiValueError( + "Invalid value '{}' for type float at {}".format(arg, validation_metadata.path_to_item) + ) + return None + # double + if not __double_inclusive_minimum <= arg <= __double_inclusive_maximum: + raise exceptions.ApiValueError( + "Invalid value '{}' for type double at {}".format(arg, validation_metadata.path_to_item) + ) + return None + return None + + +def __validate_string_format( + arg: str, + format_value: str, + validation_metadata: ValidationMetadata +) -> None: + if format_value == 'uuid': + try: + uuid.UUID(arg) + return None + except ValueError: + raise exceptions.ApiValueError( + "Invalid value '{}' for type UUID at {}".format(arg, validation_metadata.path_to_item) + ) + elif format_value == 'number': + try: + decimal.Decimal(arg) + return None + except decimal.InvalidOperation: + raise exceptions.ApiValueError( + "Value cannot be converted to a decimal. " + "Invalid value '{}' for type decimal at {}".format(arg, validation_metadata.path_to_item) + ) + elif format_value == 'date': + try: + format.DEFAULT_ISOPARSER.parse_isodate_str(arg) + return None + except ValueError: + raise exceptions.ApiValueError( + "Value does not conform to the required ISO-8601 date format. " + "Invalid value '{}' for type date at {}".format(arg, validation_metadata.path_to_item) + ) + elif format_value == 'date-time': + try: + format.DEFAULT_ISOPARSER.parse_isodatetime(arg) + return None + except ValueError: + raise exceptions.ApiValueError( + "Value does not conform to the required ISO-8601 datetime format. " + "Invalid value '{}' for type datetime at {}".format(arg, validation_metadata.path_to_item) + ) + return None + + +def validate_format( + arg: typing.Union[str, int, float], + format_value: str, + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + # formats work for strings + numbers + if isinstance(arg, (int, float)): + return __validate_numeric_format( + arg, + format_value, + validation_metadata + ) + elif isinstance(arg, str): + return __validate_string_format( + arg, + format_value, + validation_metadata + ) + return None + + +def validate_required( + arg: typing.Any, + required: typing.Set[str], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, immutabledict): + return None + missing_req_args = required - arg.keys() + if missing_req_args: + missing_required_arguments = list(missing_req_args) + missing_required_arguments.sort() + raise exceptions.ApiTypeError( + "{} is missing {} required argument{}: {}".format( + cls.__name__, + len(missing_required_arguments), + "s" if len(missing_required_arguments) > 1 else "", + missing_required_arguments + ) + ) + return None + + +def validate_items( + arg: typing.Any, + item_cls_prefix_items_length: typing.Tuple[typing.Type[SchemaValidator], int], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + item_cls = _get_class(item_cls_prefix_items_length[0]) + prefix_items_length = item_cls_prefix_items_length[1] + path_to_schemas: PathToSchemasType = {} + for i in range(prefix_items_length, len(arg)): + value = arg[i] + item_validation_metadata = ValidationMetadata( + path_to_item=validation_metadata.path_to_item+(i,), + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + if item_validation_metadata.validation_ran_earlier(item_cls): + add_deeper_validated_schemas(item_validation_metadata, path_to_schemas) + continue + other_path_to_schemas = item_cls._validate( + value, validation_metadata=item_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_properties( + arg: typing.Any, + properties: typing.Mapping[str, typing.Type[SchemaValidator]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + path_to_schemas: PathToSchemasType = {} + present_properties = {k: v for k, v, in arg.items() if k in properties} + module_namespace = vars(sys.modules[cls.__module__]) + for property_name, value in present_properties.items(): + path_to_item = validation_metadata.path_to_item + (property_name,) + schema = properties[property_name] + schema = _get_class(schema, module_namespace) + arg_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(value, validation_metadata=arg_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_additional_properties( + arg: typing.Any, + additional_properties_cls_val_pprops: typing.Tuple[ + typing.Type[SchemaValidator], + typing.Optional[PathToSchemasType] + ], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + schema = _get_class(additional_properties_cls_val_pprops[0]) + path_to_schemas: PathToSchemasType = {} + cls_schema = cls() + properties = cls_schema.properties if hasattr(cls_schema, 'properties') else {} + present_additional_properties = {k: v for k, v, in arg.items() if k not in properties} + validated_pattern_properties = additional_properties_cls_val_pprops[1] + for property_name, value in present_additional_properties.items(): + path_to_item = validation_metadata.path_to_item + (property_name,) + if validated_pattern_properties and path_to_item in validated_pattern_properties: + continue + arg_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(value, validation_metadata=arg_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_one_of( + arg: typing.Any, + classes: typing.Tuple[typing.Type[SchemaValidator], ...], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> PathToSchemasType: + oneof_classes = [] + path_to_schemas: PathToSchemasType = collections.defaultdict(dict) + for schema in classes: + schema = _get_class(schema) + if schema in path_to_schemas[validation_metadata.path_to_item]: + oneof_classes.append(schema) + continue + if schema is cls: + """ + optimistically assume that cls schema will pass validation + do not invoke _validate on it because that is recursive + """ + oneof_classes.append(schema) + continue + if validation_metadata.validation_ran_earlier(schema): + oneof_classes.append(schema) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + try: + path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata) + except (exceptions.ApiValueError, exceptions.ApiTypeError) as ex: + # silence exceptions because the code needs to accumulate oneof_classes + continue + oneof_classes.append(schema) + if not oneof_classes: + raise exceptions.ApiValueError( + "Invalid inputs given to generate an instance of {}. None " + "of the oneOf schemas matched the input data.".format(cls) + ) + elif len(oneof_classes) > 1: + raise exceptions.ApiValueError( + "Invalid inputs given to generate an instance of {}. Multiple " + "oneOf schemas {} matched the inputs, but a max of one is allowed.".format(cls, oneof_classes) + ) + # exactly one class matches + return path_to_schemas + + +def validate_any_of( + arg: typing.Any, + classes: typing.Tuple[typing.Type[SchemaValidator], ...], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> PathToSchemasType: + anyof_classes = [] + path_to_schemas: PathToSchemasType = collections.defaultdict(dict) + module_namespace = vars(sys.modules[cls.__module__]) + for schema in classes: + schema = _get_class(schema, module_namespace) + if schema is cls: + """ + optimistically assume that cls schema will pass validation + do not invoke _validate on it because that is recursive + """ + anyof_classes.append(schema) + continue + if validation_metadata.validation_ran_earlier(schema): + anyof_classes.append(schema) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + + try: + other_path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata) + except (exceptions.ApiValueError, exceptions.ApiTypeError) as ex: + # silence exceptions because the code needs to accumulate anyof_classes + continue + anyof_classes.append(schema) + update(path_to_schemas, other_path_to_schemas) + if not anyof_classes: + raise exceptions.ApiValueError( + "Invalid inputs given to generate an instance of {}. None " + "of the anyOf schemas matched the input data.".format(cls) + ) + return path_to_schemas + + +def validate_all_of( + arg: typing.Any, + classes: typing.Tuple[typing.Type[SchemaValidator], ...], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> PathToSchemasType: + path_to_schemas: PathToSchemasType = collections.defaultdict(dict) + for schema in classes: + schema = _get_class(schema) + if schema is cls: + """ + optimistically assume that cls schema will pass validation + do not invoke _validate on it because that is recursive + """ + continue + if validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_not( + arg: typing.Any, + not_cls: typing.Type[SchemaValidator], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + not_schema = _get_class(not_cls) + other_path_to_schemas = None + not_exception = exceptions.ApiValueError( + "Invalid value '{}' was passed in to {}. Value is invalid because it is disallowed by {}".format( + arg, + cls.__name__, + not_schema.__name__, + ) + ) + if validation_metadata.validation_ran_earlier(not_schema): + raise not_exception + + try: + other_path_to_schemas = not_schema._validate(arg, validation_metadata=validation_metadata) + except (exceptions.ApiValueError, exceptions.ApiTypeError): + pass + if other_path_to_schemas: + raise not_exception + return None + + +def __ensure_discriminator_value_present( + disc_property_name: str, + validation_metadata: ValidationMetadata, + arg +): + if disc_property_name not in arg: + # The input data does not contain the discriminator property + raise exceptions.ApiValueError( + "Cannot deserialize input data due to missing discriminator. " + "The discriminator property '{}' is missing at path: {}".format(disc_property_name, validation_metadata.path_to_item) + ) + + +def __get_discriminated_class(cls, disc_property_name: str, disc_payload_value: str): + """ + Used in schemas with discriminators + """ + cls_schema = cls() + if not hasattr(cls_schema, 'discriminator'): + return None + disc = cls_schema.discriminator + if disc_property_name not in disc: + return None + discriminated_cls = disc[disc_property_name].get(disc_payload_value) + if discriminated_cls is not None: + return discriminated_cls + if not ( + hasattr(cls_schema, 'all_of') or + hasattr(cls_schema, 'one_of') or + hasattr(cls_schema, 'any_of') + ): + return None + # TODO stop traveling if a cycle is hit + if hasattr(cls_schema, 'all_of'): + for allof_cls in cls_schema.all_of: + discriminated_cls = __get_discriminated_class( + allof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) + if discriminated_cls is not None: + return discriminated_cls + if hasattr(cls_schema, 'one_of'): + for oneof_cls in cls_schema.one_of: + discriminated_cls = __get_discriminated_class( + oneof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) + if discriminated_cls is not None: + return discriminated_cls + if hasattr(cls_schema, 'any_of'): + for anyof_cls in cls_schema.any_of: + discriminated_cls = __get_discriminated_class( + anyof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) + if discriminated_cls is not None: + return discriminated_cls + return None + + +def validate_discriminator( + arg: typing.Any, + discriminator: typing.Mapping[str, typing.Mapping[str, typing.Type[SchemaValidator]]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + disc_prop_name = list(discriminator.keys())[0] + __ensure_discriminator_value_present(disc_prop_name, validation_metadata, arg) + discriminated_cls = __get_discriminated_class( + cls, disc_property_name=disc_prop_name, disc_payload_value=arg[disc_prop_name] + ) + if discriminated_cls is None: + raise exceptions.ApiValueError( + "Invalid discriminator value was passed in to {}.{} Only the values {} are allowed at {}".format( + cls.__name__, + disc_prop_name, + list(discriminator[disc_prop_name].keys()), + validation_metadata.path_to_item + (disc_prop_name,) + ) + ) + if discriminated_cls is cls: + """ + Optimistically assume that cls will pass validation + If the code invoked _validate on cls it would infinitely recurse + """ + return None + if validation_metadata.validation_ran_earlier(discriminated_cls): + path_to_schemas: PathToSchemasType = {} + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + return path_to_schemas + updated_vm = ValidationMetadata( + path_to_item=validation_metadata.path_to_item, + configuration=validation_metadata.configuration, + seen_classes=validation_metadata.seen_classes | frozenset({cls}), + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + return discriminated_cls._validate(arg, validation_metadata=updated_vm) + + +def _get_if_path_to_schemas( + arg: typing.Any, + if_cls: typing.Type[SchemaValidator], + validation_metadata: ValidationMetadata, +) -> PathToSchemasType: + if_cls = _get_class(if_cls) + these_path_to_schemas: PathToSchemasType = {} + try: + other_path_to_schemas = if_cls._validate( + arg, validation_metadata=validation_metadata) + update(these_path_to_schemas, other_path_to_schemas) + except exceptions.OpenApiException: + pass + return these_path_to_schemas + + +def validate_if( + arg: typing.Any, + if_cls_if_path_to_schemas: typing.Tuple[ + typing.Type[SchemaValidator], typing.Optional[PathToSchemasType] + ], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if_path_to_schemas = if_cls_if_path_to_schemas[1] + if if_path_to_schemas is None: + raise exceptions.OpenApiException('Invalid type for if_path_to_schemas') + """ + if is false use case + if_path_to_schemas == {} + no need to add any data to path_to_schemas + + if true, then true -> true for whole schema + so validate_then will add if_path_to_schemas data to path_to_schemas + """ + return None + + +def validate_then( + arg: typing.Any, + then_cls_if_path_to_schemas: typing.Tuple[ + typing.Type[SchemaValidator], typing.Optional[PathToSchemasType] + ], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if_path_to_schemas = then_cls_if_path_to_schemas[1] + if if_path_to_schemas is None: + # use case: there is no if + return None + """ + if is false use case + if_path_to_schemas == {} + no need to add any data to path_to_schemas + """ + if not if_path_to_schemas: + return None + then_cls = _get_class(then_cls_if_path_to_schemas[0]) + these_path_to_schemas: PathToSchemasType = {} + try: + other_path_to_schemas = then_cls._validate( + arg, validation_metadata=validation_metadata) + update(these_path_to_schemas, if_path_to_schemas) + update(these_path_to_schemas, other_path_to_schemas) + return these_path_to_schemas + except exceptions.OpenApiException as ex: + # then False case + raise ex + + +def validate_else( + arg: typing.Any, + else_cls_if_path_to_schemas: typing.Tuple[ + typing.Type[SchemaValidator], typing.Optional[PathToSchemasType] + ], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if_path_to_schemas = else_cls_if_path_to_schemas[1] + if if_path_to_schemas is None: + # use case: there is no if + return None + if if_path_to_schemas: + # skip validation if if_path_to_schemas was true + return None + """ + if is false use case + if_path_to_schemas == {} + """ + else_cls = _get_class(else_cls_if_path_to_schemas[0]) + these_path_to_schemas: PathToSchemasType = {} + try: + other_path_to_schemas = else_cls._validate( + arg, validation_metadata=validation_metadata) + update(these_path_to_schemas, if_path_to_schemas) + update(these_path_to_schemas, other_path_to_schemas) + return these_path_to_schemas + except exceptions.OpenApiException as ex: + # else False case + raise ex + + +def _get_contains_path_to_schemas( + arg: typing.Any, + contains_cls: typing.Type[SchemaValidator], + validation_metadata: ValidationMetadata, + path_to_schemas: PathToSchemasType +) -> typing.List[PathToSchemasType]: + if not isinstance(arg, tuple): + return [] + contains_cls = _get_class(contains_cls) + contains_path_to_schemas = [] + for i, value in enumerate(arg): + these_path_to_schemas: PathToSchemasType = {} + item_validation_metadata = ValidationMetadata( + path_to_item=validation_metadata.path_to_item+(i,), + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + if item_validation_metadata.validation_ran_earlier(contains_cls): + add_deeper_validated_schemas(item_validation_metadata, these_path_to_schemas) + contains_path_to_schemas.append(these_path_to_schemas) + continue + try: + other_path_to_schemas = contains_cls._validate( + value, validation_metadata=item_validation_metadata) + contains_path_to_schemas.append(other_path_to_schemas) + except exceptions.OpenApiException: + pass + return contains_path_to_schemas + + +def validate_contains( + arg: typing.Any, + contains_cls_path_to_schemas: typing.Tuple[typing.Type[SchemaValidator], typing.List[PathToSchemasType]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + many_path_to_schemas = contains_cls_path_to_schemas[1] + if not many_path_to_schemas: + raise exceptions.ApiValueError( + "Validation failed for contains keyword in class={} at path_to_item={}. No " + "items validated to the contains schema.".format(cls, validation_metadata.path_to_item) + ) + these_path_to_schemas: PathToSchemasType = {} + for other_path_to_schema in many_path_to_schemas: + update(these_path_to_schemas, other_path_to_schema) + return these_path_to_schemas + + +def validate_min_contains( + arg: typing.Any, + min_contains_and_contains_path_to_schemas: typing.Tuple[int, typing.List[PathToSchemasType]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + min_contains = min_contains_and_contains_path_to_schemas[0] + contains_path_to_schemas = min_contains_and_contains_path_to_schemas[1] + if len(contains_path_to_schemas) < min_contains: + raise exceptions.ApiValueError( + "Validation failed for minContains keyword in class={} at path_to_item={}. No " + "items validated to the contains schema.".format(cls, validation_metadata.path_to_item) + ) + return None + + +def validate_max_contains( + arg: typing.Any, + max_contains_and_contains_path_to_schemas: typing.Tuple[int, typing.List[PathToSchemasType]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + max_contains = max_contains_and_contains_path_to_schemas[0] + contains_path_to_schemas = max_contains_and_contains_path_to_schemas[1] + if len(contains_path_to_schemas) > max_contains: + raise exceptions.ApiValueError( + "Validation failed for maxContains keyword in class={} at path_to_item={}. Too " + "many items validated to the contains schema.".format(cls, validation_metadata.path_to_item) + ) + return None + + +def validate_const( + arg: typing.Any, + const_value_to_name: typing.Dict[typing.Any, str], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if arg not in const_value_to_name: + raise exceptions.ApiValueError("Invalid value {} passed in to {}, allowed_values={}".format(arg, cls, const_value_to_name.keys())) + return None + + +def validate_dependent_required( + arg: typing.Any, + dependent_required: typing.Mapping[str, typing.Set[str]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, immutabledict): + return None + for key, keys_that_must_exist in dependent_required.items(): + if key not in arg: + continue + missing_keys = keys_that_must_exist - arg.keys() + if missing_keys: + raise exceptions.ApiValueError( + f"Validation failed for dependentRequired because these_keys={missing_keys} are " + f"missing at path_to_item={validation_metadata.path_to_item} in class {cls}" + ) + return None + + +def validate_dependent_schemas( + arg: typing.Any, + dependent_schemas: typing.Mapping[str, typing.Type[SchemaValidator]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + path_to_schemas: PathToSchemasType = {} + module_namespace = vars(sys.modules[cls.__module__]) + for key, schema in dependent_schemas.items(): + if key not in arg: + continue + schema = _get_class(schema, module_namespace) + if validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_property_names( + arg: typing.Any, + property_names_schema: typing.Type[SchemaValidator], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> None: + if not isinstance(arg, immutabledict): + return None + module_namespace = vars(sys.modules[cls.__module__]) + property_names_schema = _get_class(property_names_schema, module_namespace) + for key in arg.keys(): + path_to_item = validation_metadata.path_to_item + (key,) + key_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + property_names_schema._validate(key, validation_metadata=key_validation_metadata) + return None + + +def _get_validated_pattern_properties( + arg: typing.Any, + pattern_properties: typing.Mapping[PatternInfo, typing.Type[SchemaValidator]], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + path_to_schemas: PathToSchemasType = {} + module_namespace = vars(sys.modules[cls.__module__]) + for property_name, property_value in arg.items(): + path_to_item = validation_metadata.path_to_item + (property_name,) + property_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + for pattern_info, schema in pattern_properties.items(): + flags = pattern_info.flags if pattern_info.flags is not None else 0 + if not re.search(pattern_info.pattern, property_name, flags=flags): + continue + schema = _get_class(schema, module_namespace) + if validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(property_value, validation_metadata=property_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_pattern_properties( + arg: typing.Any, + pattern_properties_validation_results: typing.Tuple[ + typing.Mapping[PatternInfo, typing.Type[SchemaValidator]], + typing.Optional[PathToSchemasType] + ], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + validation_results = pattern_properties_validation_results[1] + return validation_results + + +def validate_prefix_items( + arg: typing.Any, + prefix_items: typing.Tuple[typing.Type[SchemaValidator], ...], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + path_to_schemas: PathToSchemasType = {} + module_namespace = vars(sys.modules[cls.__module__]) + for i, val in enumerate(arg): + if i >= len(prefix_items): + break + schema = _get_class(prefix_items[i], module_namespace) + path_to_item = validation_metadata.path_to_item + (i,) + item_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + if item_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) + continue + other_path_to_schemas = schema._validate(val, validation_metadata=item_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_unevaluated_items( + arg: typing.Any, + unevaluated_items_validated_path_to_schemas: typing.Tuple[typing.Type[SchemaValidator], PathToSchemasType], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, tuple): + return None + path_to_schemas: PathToSchemasType = {} + module_namespace = vars(sys.modules[cls.__module__]) + schema = _get_class(unevaluated_items_validated_path_to_schemas[0], module_namespace) + validated_path_to_schemas = unevaluated_items_validated_path_to_schemas[1] + for i, val in enumerate(arg): + path_to_item = validation_metadata.path_to_item + (i,) + if path_to_item in validated_path_to_schemas: + continue + item_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + other_path_to_schemas = schema._validate(val, validation_metadata=item_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +def validate_unevaluated_properties( + arg: typing.Any, + unevaluated_properties_validated_path_to_schemas: typing.Tuple[typing.Type[SchemaValidator], PathToSchemasType], + cls: typing.Type, + validation_metadata: ValidationMetadata, +) -> typing.Optional[PathToSchemasType]: + if not isinstance(arg, immutabledict): + return None + path_to_schemas: PathToSchemasType = {} + module_namespace = vars(sys.modules[cls.__module__]) + schema = _get_class(unevaluated_properties_validated_path_to_schemas[0], module_namespace) + validated_path_to_schemas = unevaluated_properties_validated_path_to_schemas[1] + for property_name, val in arg.items(): + path_to_item = validation_metadata.path_to_item + (property_name,) + if path_to_item in validated_path_to_schemas: + continue + property_validation_metadata = ValidationMetadata( + path_to_item=path_to_item, + configuration=validation_metadata.configuration, + validated_path_to_schemas=validation_metadata.validated_path_to_schemas + ) + other_path_to_schemas = schema._validate(val, validation_metadata=property_validation_metadata) + update(path_to_schemas, other_path_to_schemas) + return path_to_schemas + + +validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]] +json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = { + 'types': validate_types, + 'enum_value_to_name': validate_enum, + 'unique_items': validate_unique_items, + 'min_items': validate_min_items, + 'max_items': validate_max_items, + 'min_properties': validate_min_properties, + 'max_properties': validate_max_properties, + 'min_length': validate_min_length, + 'max_length': validate_max_length, + 'inclusive_minimum': validate_inclusive_minimum, + 'exclusive_minimum': validate_exclusive_minimum, + 'inclusive_maximum': validate_inclusive_maximum, + 'exclusive_maximum': validate_exclusive_maximum, + 'multiple_of': validate_multiple_of, + 'pattern': validate_pattern, + 'format': validate_format, + 'required': validate_required, + 'items': validate_items, + 'properties': validate_properties, + 'additional_properties': validate_additional_properties, + 'one_of': validate_one_of, + 'any_of': validate_any_of, + 'all_of': validate_all_of, + 'not_': validate_not, + 'discriminator': validate_discriminator, + 'contains': validate_contains, + 'min_contains': validate_min_contains, + 'max_contains': validate_max_contains, + 'const_value_to_name': validate_const, + 'dependent_required': validate_dependent_required, + 'dependent_schemas': validate_dependent_schemas, + 'property_names': validate_property_names, + 'pattern_properties': validate_pattern_properties, + 'prefix_items': validate_prefix_items, + 'unevaluated_items': validate_unevaluated_items, + 'unevaluated_properties': validate_unevaluated_properties, + 'if_': validate_if, + 'then': validate_then, + 'else_': validate_else +} \ No newline at end of file diff --git a/comfy/api/security_schemes.py b/comfy/api/security_schemes.py new file mode 100644 index 000000000..8373c5446 --- /dev/null +++ b/comfy/api/security_schemes.py @@ -0,0 +1,227 @@ +# coding: utf-8 +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +import abc +import base64 +import dataclasses +import enum +import typing +import typing_extensions + +from urllib3 import _collections + + +class SecuritySchemeType(enum.Enum): + API_KEY = 'apiKey' + HTTP = 'http' + MUTUAL_TLS = 'mutualTLS' + OAUTH_2 = 'oauth2' + OPENID_CONNECT = 'openIdConnect' + + +class ApiKeyInLocation(enum.Enum): + QUERY = 'query' + HEADER = 'header' + COOKIE = 'cookie' + + +class __SecuritySchemeBase(metaclass=abc.ABCMeta): + @abc.abstractmethod + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + pass + + +@dataclasses.dataclass +class ApiKeySecurityScheme(__SecuritySchemeBase, abc.ABC): + api_key: str # this must be set by the developer + name: str = '' + in_location: ApiKeyInLocation = ApiKeyInLocation.QUERY + type: SecuritySchemeType = SecuritySchemeType.API_KEY + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + if self.in_location is ApiKeyInLocation.COOKIE: + headers.add('Cookie', self.api_key) + elif self.in_location is ApiKeyInLocation.HEADER: + headers.add(self.name, self.api_key) + elif self.in_location is ApiKeyInLocation.QUERY: + # todo add query handling + raise NotImplementedError("ApiKeySecurityScheme in query not yet implemented") + return + + +class HTTPSchemeType(enum.Enum): + BASIC = 'basic' + BEARER = 'bearer' + DIGEST = 'digest' + SIGNATURE = 'signature' # https://datatracker.ietf.org/doc/draft-cavage-http-signatures/ + + +@dataclasses.dataclass +class HTTPBasicSecurityScheme(__SecuritySchemeBase): + user_id: str # user name + password: str + scheme: HTTPSchemeType = HTTPSchemeType.BASIC + encoding: str = 'utf-8' + type: SecuritySchemeType = SecuritySchemeType.HTTP + """ + https://www.rfc-editor.org/rfc/rfc7617.html + """ + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + user_pass = f"{self.user_id}:{self.password}" + b64_user_pass = base64.b64encode(user_pass.encode(encoding=self.encoding)) + headers.add('Authorization', f"Basic {b64_user_pass.decode()}") + + +@dataclasses.dataclass +class HTTPBearerSecurityScheme(__SecuritySchemeBase): + access_token: str + bearer_format: typing.Optional[str] = None + scheme: HTTPSchemeType = HTTPSchemeType.BEARER + type: SecuritySchemeType = SecuritySchemeType.HTTP + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + headers.add('Authorization', f"Bearer {self.access_token}") + + +@dataclasses.dataclass +class HTTPDigestSecurityScheme(__SecuritySchemeBase): + scheme: HTTPSchemeType = HTTPSchemeType.DIGEST + type: SecuritySchemeType = SecuritySchemeType.HTTP + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + raise NotImplementedError("HTTPDigestSecurityScheme not yet implemented") + + +@dataclasses.dataclass +class MutualTLSSecurityScheme(__SecuritySchemeBase): + type: SecuritySchemeType = SecuritySchemeType.MUTUAL_TLS + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + raise NotImplementedError("MutualTLSSecurityScheme not yet implemented") + + +@dataclasses.dataclass +class ImplicitOAuthFlow: + authorization_url: str + scopes: typing.Dict[str, str] + refresh_url: typing.Optional[str] = None + + +@dataclasses.dataclass +class TokenUrlOauthFlow: + token_url: str + scopes: typing.Dict[str, str] + refresh_url: typing.Optional[str] = None + + +@dataclasses.dataclass +class AuthorizationCodeOauthFlow: + authorization_url: str + token_url: str + scopes: typing.Dict[str, str] + refresh_url: typing.Optional[str] = None + + +@dataclasses.dataclass +class OAuthFlows: + implicit: typing.Optional[ImplicitOAuthFlow] = None + password: typing.Optional[TokenUrlOauthFlow] = None + client_credentials: typing.Optional[TokenUrlOauthFlow] = None + authorization_code: typing.Optional[AuthorizationCodeOauthFlow] = None + + +class OAuth2SecurityScheme(__SecuritySchemeBase, abc.ABC): + flows: OAuthFlows + type: SecuritySchemeType = SecuritySchemeType.OAUTH_2 + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + raise NotImplementedError("OAuth2SecurityScheme not yet implemented") + + +class OpenIdConnectSecurityScheme(__SecuritySchemeBase, abc.ABC): + openid_connect_url: str + type: SecuritySchemeType = SecuritySchemeType.OPENID_CONNECT + + def apply_auth( + self, + headers: _collections.HTTPHeaderDict, + resource_path: str, + method: str, + body: typing.Optional[typing.Union[str, bytes]], + query_params_suffix: typing.Optional[str], + scope_names: typing.Tuple[str, ...] = (), + ) -> None: + raise NotImplementedError("OpenIdConnectSecurityScheme not yet implemented") + +""" +Key is the Security scheme class +Value is the list of scopes +""" +SecurityRequirementObject = typing.TypedDict( + 'SecurityRequirementObject', + { + }, + total=False +) \ No newline at end of file diff --git a/comfy/api/server.py b/comfy/api/server.py new file mode 100644 index 000000000..affdfe65b --- /dev/null +++ b/comfy/api/server.py @@ -0,0 +1,34 @@ +# coding: utf-8 +""" + comfyui + No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501 + The version of the OpenAPI document: 0.0.1 + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from __future__ import annotations +import abc +import dataclasses +import typing + +from comfy.api.schemas import validation, schema + + +@dataclasses.dataclass +class ServerWithoutVariables(abc.ABC): + url: str + + +@dataclasses.dataclass +class ServerWithVariables(abc.ABC): + _url: str + variables: validation.immutabledict[str, str] + variables_schema: typing.Type[schema.Schema] + url: str = dataclasses.field(init=False) + + def __post_init__(self): + url = self._url + assert isinstance (self.variables, self.variables_schema().type_to_output_cls[validation.immutabledict]) + for (key, value) in self.variables.items(): + url = url.replace("{" + key + "}", value) + self.url = url diff --git a/comfy/api/servers/__init__.py b/comfy/api/servers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/servers/server_0.py b/comfy/api/servers/server_0.py new file mode 100644 index 000000000..27bc06d1d --- /dev/null +++ b/comfy/api/servers/server_0.py @@ -0,0 +1,14 @@ +# coding: utf-8 +""" + Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator +""" + +from comfy.api.shared_imports.server_imports import * # pyright: ignore [reportWildcardImportFromLibrary] + + +@dataclasses.dataclass +class Server0(server.ServerWithoutVariables): + ''' + localhost + ''' + url: str = "http://localhost:8188" diff --git a/comfy/api/shared_imports/__init__.py b/comfy/api/shared_imports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/api/shared_imports/header_imports.py b/comfy/api/shared_imports/header_imports.py new file mode 100644 index 000000000..831af1f72 --- /dev/null +++ b/comfy/api/shared_imports/header_imports.py @@ -0,0 +1,15 @@ +import decimal +import io +import typing +import typing_extensions + +from comfy.api import api_client, schemas + +__all__ = [ + 'decimal', + 'io', + 'typing', + 'typing_extensions', + 'api_client', + 'schemas' +] \ No newline at end of file diff --git a/comfy/api/shared_imports/operation_imports.py b/comfy/api/shared_imports/operation_imports.py new file mode 100644 index 000000000..fdeea198d --- /dev/null +++ b/comfy/api/shared_imports/operation_imports.py @@ -0,0 +1,18 @@ +import datetime +import decimal +import io +import typing +import typing_extensions +import uuid + +from comfy.api import schemas, api_response + +__all__ = [ + 'decimal', + 'io', + 'typing', + 'typing_extensions', + 'uuid', + 'schemas', + 'api_response' +] \ No newline at end of file diff --git a/comfy/api/shared_imports/response_imports.py b/comfy/api/shared_imports/response_imports.py new file mode 100644 index 000000000..df2e1e336 --- /dev/null +++ b/comfy/api/shared_imports/response_imports.py @@ -0,0 +1,25 @@ +import dataclasses +import datetime +import decimal +import io +import typing +import uuid + +import typing_extensions +import urllib3 + +from comfy.api import api_client, schemas, api_response + +__all__ = [ + 'dataclasses', + 'datetime', + 'decimal', + 'io', + 'typing', + 'uuid', + 'typing_extensions', + 'urllib3', + 'api_client', + 'schemas', + 'api_response' +] \ No newline at end of file diff --git a/comfy/api/shared_imports/schema_imports.py b/comfy/api/shared_imports/schema_imports.py new file mode 100644 index 000000000..266982936 --- /dev/null +++ b/comfy/api/shared_imports/schema_imports.py @@ -0,0 +1,28 @@ +import dataclasses +import datetime +import decimal +import io +import numbers +import re +import typing +import typing_extensions +import uuid + +from comfy.api import schemas +from comfy.api.configurations import schema_configuration + +U = typing.TypeVar('U') + +__all__ = [ + 'dataclasses', + 'datetime', + 'decimal', + 'io', + 'numbers', + 're', + 'typing', + 'typing_extensions', + 'uuid', + 'schemas', + 'schema_configuration' +] \ No newline at end of file diff --git a/comfy/api/shared_imports/security_scheme_imports.py b/comfy/api/shared_imports/security_scheme_imports.py new file mode 100644 index 000000000..6bc61fdb3 --- /dev/null +++ b/comfy/api/shared_imports/security_scheme_imports.py @@ -0,0 +1,12 @@ +import dataclasses +import typing +import typing_extensions + +from comfy.api import security_schemes + +__all__ = [ + 'dataclasses', + 'typing', + 'typing_extensions', + 'security_schemes' +] \ No newline at end of file diff --git a/comfy/api/shared_imports/server_imports.py b/comfy/api/shared_imports/server_imports.py new file mode 100644 index 000000000..983bf7e96 --- /dev/null +++ b/comfy/api/shared_imports/server_imports.py @@ -0,0 +1,13 @@ +import dataclasses +import typing +import typing_extensions + +from comfy.api import server, schemas + +__all__ = [ + 'dataclasses', + 'typing', + 'typing_extensions', + 'server', + 'schemas' +] \ No newline at end of file diff --git a/comfy/cli_args.py b/comfy/cli_args.py index ca4ffcd3a..fa21d6f4d 100644 --- a/comfy/cli_args.py +++ b/comfy/cli_args.py @@ -1,6 +1,8 @@ import configargparse as argparse import enum from . import options +from .cli_args_types import LatentPreviewMethod, Configuration + class EnumAction(argparse.Action): """ @@ -31,9 +33,11 @@ class EnumAction(argparse.Action): setattr(namespace, self.dest, value) -parser = argparse.ArgumentParser(default_config_files=['config.yaml', 'config.json'], auto_env_var_prefix='COMFYUI_') +parser = argparse.ArgumentParser(default_config_files=['config.yaml', 'config.json'], auto_env_var_prefix='COMFYUI_', + args_for_setting_config_path=["-c", "--config"], + add_env_var_help=True, add_config_file_help=True, add_help=True, + args_for_writing_out_config_file=["--write-out-config-file"]) -parser.add_argument('-c', '--config', required=False, default=None, is_config_file=True, help='Specify additional configuration files.') parser.add_argument('-w', "--cwd", type=str, default=None, help="Specify the working directory. If not set, this is the current working directory. models/, input/, output/ and other directories will be located here by default.") parser.add_argument('-H', "--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0", help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)") parser.add_argument("--port", type=int, default=8188, help="Set the listen port.") @@ -75,17 +79,10 @@ fpte_group.add_argument("--fp8_e5m2-text-enc", action="store_true", help="Store fpte_group.add_argument("--fp16-text-enc", action="store_true", help="Store text encoder weights in fp16.") fpte_group.add_argument("--fp32-text-enc", action="store_true", help="Store text encoder weights in fp32.") - parser.add_argument("--directml", type=int, nargs="?", metavar="DIRECTML_DEVICE", const=-1, help="Use torch-directml.") parser.add_argument("--disable-ipex-optimize", action="store_true", help="Disables ipex.optimize when loading models with Intel GPUs.") -class LatentPreviewMethod(enum.Enum): - NoPreviews = "none" - Auto = "auto" - Latent2RGB = "latent2rgb" - TAESD = "taesd" - parser.add_argument("--preview-method", type=LatentPreviewMethod, default=LatentPreviewMethod.NoPreviews, help="Default preview method for sampler nodes.", action=EnumAction) attn_group = parser.add_mutually_exclusive_group() @@ -115,13 +112,21 @@ parser.add_argument("--disable-metadata", action="store_true", help="Disable sav parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.") +parser.add_argument("--plausible-analytics-base-url", required=False, + help="Enables server-side analytics events sent to the provided URL.") +parser.add_argument("--plausible-analytics-domain", required=False, + help="Specifies the domain name for analytics events.") +parser.add_argument("--analytics-use-identity-provider", action="store_true", + help="Uses platform identifiers for unique visitor analytics.") if options.args_parsing: - args = parser.parse_args() + args, _ = parser.parse_known_args() else: - args = parser.parse_args([]) + args, _ = parser.parse_known_args([]) if args.windows_standalone_build: args.auto_launch = True if args.disable_auto_launch: args.auto_launch = False + +args = Configuration(**vars(args)) diff --git a/comfy/cli_args_types.py b/comfy/cli_args_types.py new file mode 100644 index 000000000..b30cefc13 --- /dev/null +++ b/comfy/cli_args_types.py @@ -0,0 +1,137 @@ +# Define a class for your command-line arguments +import enum +from typing import Optional, List, TypedDict + + +class LatentPreviewMethod(enum.Enum): + NoPreviews = "none" + Auto = "auto" + Latent2RGB = "latent2rgb" + TAESD = "taesd" + + +class Configuration(dict): + """ + Configuration options parsed from command-line arguments or config files. + + Attributes: + config (Optional[str]): Path to the configuration file. + cwd (Optional[str]): Working directory. Defaults to the current directory. + listen (str): IP address to listen on. Defaults to "127.0.0.1". + port (int): Port number for the server to listen on. Defaults to 8188. + enable_cors_header (Optional[str]): Enables CORS with the specified origin. + max_upload_size (float): Maximum upload size in MB. Defaults to 100. + extra_model_paths_config (Optional[List[str]]): Extra model paths configuration files. + output_directory (Optional[str]): Directory for output files. + temp_directory (Optional[str]): Temporary directory for processing. + input_directory (Optional[str]): Directory for input files. + auto_launch (bool): Auto-launch UI in the default browser. Defaults to False. + disable_auto_launch (bool): Disable auto-launching the browser. + cuda_device (Optional[int]): CUDA device ID. None means default device. + cuda_malloc (bool): Enable cudaMallocAsync. Defaults to True in applicable setups. + disable_cuda_malloc (bool): Disable cudaMallocAsync. + dont_upcast_attention (bool): Disable upcasting of attention. + force_fp32 (bool): Force using FP32 precision. + force_fp16 (bool): Force using FP16 precision. + bf16_unet (bool): Use BF16 precision for UNet. + fp16_unet (bool): Use FP16 precision for UNet. + fp8_e4m3fn_unet (bool): Use FP8 precision (e4m3fn variant) for UNet. + fp8_e5m2_unet (bool): Use FP8 precision (e5m2 variant) for UNet. + fp16_vae (bool): Run the VAE in FP16 precision. + fp32_vae (bool): Run the VAE in FP32 precision. + bf16_vae (bool): Run the VAE in BF16 precision. + cpu_vae (bool): Run the VAE on the CPU. + fp8_e4m3fn_text_enc (bool): Use FP8 precision for the text encoder (e4m3fn variant). + fp8_e5m2_text_enc (bool): Use FP8 precision for the text encoder (e5m2 variant). + fp16_text_enc (bool): Use FP16 precision for the text encoder. + fp32_text_enc (bool): Use FP32 precision for the text encoder. + directml (Optional[int]): Use DirectML. -1 for auto-selection. + disable_ipex_optimize (bool): Disable IPEX optimization for Intel GPUs. + preview_method (LatentPreviewMethod): Method for generating previews. Defaults to "none". + use_split_cross_attention (bool): Use split cross-attention optimization. + use_quad_cross_attention (bool): Use sub-quadratic cross-attention optimization. + use_pytorch_cross_attention (bool): Use PyTorch's cross-attention function. + disable_xformers (bool): Disable xformers. + gpu_only (bool): Run everything on the GPU. + highvram (bool): Keep models in GPU memory. + normalvram (bool): Default VRAM usage setting. + lowvram (bool): Reduce UNet's VRAM usage. + novram (bool): Minimize VRAM usage. + cpu (bool): Use CPU for processing. + disable_smart_memory (bool): Disable smart memory management. + deterministic (bool): Use deterministic algorithms where possible. + dont_print_server (bool): Suppress server output. + quick_test_for_ci (bool): Enable quick testing mode for CI. + windows_standalone_build (bool): Enable features for standalone Windows build. + disable_metadata (bool): Disable saving metadata with outputs. + multi_user (bool): Enable multi-user mode. + plausible_analytics_base_url (Optional[str]): Base URL for server-side analytics. + plausible_analytics_domain (Optional[str]): Domain for analytics events. + analytics_use_identity_provider (bool): Use platform identifiers for analytics. + write_out_config_file (bool): Enable writing out the configuration file. + """ + + config: Optional[str] + cwd: Optional[str] + listen: str + port: int + enable_cors_header: Optional[str] + max_upload_size: float + extra_model_paths_config: Optional[List[str]] + output_directory: Optional[str] + temp_directory: Optional[str] + input_directory: Optional[str] + auto_launch: bool + disable_auto_launch: bool + cuda_device: Optional[int] + cuda_malloc: bool + disable_cuda_malloc: bool + dont_upcast_attention: bool + force_fp32: bool + force_fp16: bool + bf16_unet: bool + fp16_unet: bool + fp8_e4m3fn_unet: bool + fp8_e5m2_unet: bool + fp16_vae: bool + fp32_vae: bool + bf16_vae: bool + cpu_vae: bool + fp8_e4m3fn_text_enc: bool + fp8_e5m2_text_enc: bool + fp16_text_enc: bool + fp32_text_enc: bool + directml: Optional[int] + disable_ipex_optimize: bool + preview_method: LatentPreviewMethod + use_split_cross_attention: bool + use_quad_cross_attention: bool + use_pytorch_cross_attention: bool + disable_xformers: bool + gpu_only: bool + highvram: bool + normalvram: bool + lowvram: bool + novram: bool + cpu: bool + disable_smart_memory: bool + deterministic: bool + dont_print_server: bool + quick_test_for_ci: bool + windows_standalone_build: bool + disable_metadata: bool + multi_user: bool + plausible_analytics_base_url: Optional[str] + plausible_analytics_domain: Optional[str] + analytics_use_identity_provider: bool + write_out_config_file: bool + + def __init__(self, **kwargs): + for key, value in kwargs.items(): + self[key] = value + + def __getattr__(self, item): + return self[item] + + def __setattr__(self, key, value): + self[key] = value diff --git a/comfy/client/__init__.py b/comfy/client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/client/aio_client.py b/comfy/client/aio_client.py new file mode 100644 index 000000000..f816baba1 --- /dev/null +++ b/comfy/client/aio_client.py @@ -0,0 +1,96 @@ +import asyncio +import uuid +from asyncio import AbstractEventLoop +from collections import defaultdict +from pathlib import Path +from typing import Optional, List, Dict +from urllib.parse import urlparse, urljoin + +import aiohttp +from aiohttp import WSMessage, ClientResponse + +from ..api.components.schema.prompt import PromptDict +from ..api.api_client import JSONEncoder +from ..api.components.schema.prompt_request import PromptRequest +from ..api.paths.history.get.responses.response_200.content.application_json.schema import Schema as GetHistoryDict +from ..component_model.file_output_path import file_output_path + + +class AsyncRemoteComfyClient: + __json_encoder = JSONEncoder() + + def __init__(self, server_address: str = "http://localhost:8188", client_id: str = str(uuid.uuid4()), + websocket_address: Optional[str] = None, loop: Optional[AbstractEventLoop] = None): + self.client_id = client_id + self.server_address = server_address + server_address_url = urlparse(server_address) + self.websocket_address = websocket_address if websocket_address is not None else urljoin( + f"ws://{server_address_url.hostname}:{server_address_url.port}", f"/ws?clientId={client_id}") + self.loop = loop or asyncio.get_event_loop() + + async def queue_prompt(self, prompt: PromptDict) -> bytes: + """ + Calls the API to queue a prompt. Returns the bytes of the first PNG returned by a SaveImage node. + :param prompt: + :return: + """ + prompt_json = AsyncRemoteComfyClient.__json_encoder.encode(prompt) + async with aiohttp.ClientSession() as session: + response: ClientResponse + async with session.post(urljoin(self.server_address, "/api/v1/prompts"), data=prompt_json, + headers={'Content-Type': 'application/json', 'Accept': 'image/png'}) as response: + + if response.status == 200: + return await response.read() + else: + raise RuntimeError(f"could not prompt: {response.status}: {await response.text()}") + + async def queue_prompt_ui(self, prompt: PromptDict) -> Dict[str, List[Path]]: + """ + Uses the comfyui UI API calls to retrieve a list of paths of output files + :param prompt: + :return: + """ + prompt_request = PromptRequest.validate({"prompt": prompt, "client_id": self.client_id}) + prompt_request_json = AsyncRemoteComfyClient.__json_encoder.encode(prompt_request) + async with aiohttp.ClientSession() as session: + async with session.ws_connect(self.websocket_address) as ws: + async with session.post(urljoin(self.server_address, "/prompt"), data=prompt_request_json, + headers={'Content-Type': 'application/json'}) as response: + if response.status == 200: + prompt_id = (await response.json())["prompt_id"] + else: + raise RuntimeError("could not prompt") + msg: WSMessage + async for msg in ws: + # Handle incoming messages + if msg.type == aiohttp.WSMsgType.TEXT: + msg_json = msg.json() + if msg_json["type"] == "executing": + data = msg_json["data"] + if data['node'] is None and data['prompt_id'] == prompt_id: + break + elif msg.type == aiohttp.WSMsgType.CLOSED: + break + elif msg.type == aiohttp.WSMsgType.ERROR: + break + async with session.get(urljoin(self.server_address, "/history")) as response: + if response.status == 200: + history_json = GetHistoryDict.validate(await response.json()) + + # images have filename, subfolder, type keys + # todo: use the OpenAPI spec for this when I get around to updating it + outputs_by_node_id = history_json[prompt_id].outputs + res: Dict[str, List[Path]] = {} + for node_id, output in outputs_by_node_id.items(): + if 'images' in output: + images = [] + image_dicts: List[dict] = output['images'] + for image_file_output_dict in image_dicts: + image_file_output_dict = defaultdict(None, image_file_output_dict) + filename = image_file_output_dict['filename'] + subfolder = image_file_output_dict['subfolder'] + type = image_file_output_dict['type'] + images.append(Path(file_output_path(filename, subfolder=subfolder, type=type))) + res[node_id] = images + return res diff --git a/comfy/client/embedded_comfy_client.py b/comfy/client/embedded_comfy_client.py new file mode 100644 index 000000000..420e038a6 --- /dev/null +++ b/comfy/client/embedded_comfy_client.py @@ -0,0 +1,110 @@ +import asyncio +import gc +import uuid +from asyncio import AbstractEventLoop +from concurrent.futures import ThreadPoolExecutor +from typing import Literal, Optional + +from ..api.components.schema.prompt import PromptDict +from ..cli_args_types import Configuration +from ..component_model.make_mutable import make_mutable +from ..component_model.queue_types import BinaryEventTypes +from ..component_model.executor_types import ExecutorToClientProgress, StatusMessage, ExecutingMessage + + +class ServerStub(ExecutorToClientProgress): + def __init__(self): + self.client_id = str(uuid.uuid4()) + self.last_node_id = None + self.last_prompt_id = None + + def send_sync(self, + event: Literal["status", "executing"] | BinaryEventTypes | str | None, + data: StatusMessage | ExecutingMessage | bytes | bytearray | None, sid: str | None = None): + pass + + def queue_updated(self): + pass + + +class EmbeddedComfyClient: + """ + Embedded client for comfy executing prompts as a library. + + This client manages a single-threaded executor to run long-running or blocking tasks + asynchronously without blocking the asyncio event loop. It initializes a PromptExecutor + in a dedicated thread for executing prompts and handling server-stub communications. + + Example usage: + + Asynchronous (non-blocking) usage with async-await: + ``` + prompt = dict() # ... + async with EmbeddedComfyClient() as client: + outputs = await client.queue_prompt(prompt) + + print(result) + ``` + """ + + def __init__(self, configuration: Optional[Configuration] = None, loop: Optional[AbstractEventLoop] = None): + self._server_stub = ServerStub() + self._executor = ThreadPoolExecutor(max_workers=1) + self._loop = loop or asyncio.get_event_loop() + self._configuration = configuration + # we don't want to import the executor yet + self._prompt_executor: Optional["comfy.cmd.execution.PromptExecutor"] = None + + async def __aenter__(self): + # Perform asynchronous initialization here, if needed + await self._initialize_prompt_executor() + return self + + async def __aexit__(self, *args): + # Perform cleanup here + def cleanup(): + from .. import model_management + model_management.unload_all_models() + gc.collect() + try: + model_management.soft_empty_cache() + except: + pass + + await self._loop.run_in_executor(self._executor, cleanup) + + self._executor.shutdown(wait=True) + + async def _initialize_prompt_executor(self): + # This method must be async since it's used in __aenter__ + def create_executor_in_thread(): + from .. import options + if self._configuration is None: + options.enable_args_parsing() + else: + from ..cli_args import args + args.clear() + args.update(self._configuration) + + from ..cmd.execution import PromptExecutor + + self._prompt_executor = PromptExecutor(self._server_stub) + + await self._loop.run_in_executor(self._executor, create_executor_in_thread) + + async def queue_prompt(self, prompt: PromptDict) -> dict: + prompt_id = str(uuid.uuid4()) + + def execute_prompt() -> dict: + from ..cmd.execution import validate_prompt + prompt_mut = make_mutable(prompt) + validation_tuple = validate_prompt(prompt_mut) + + self._prompt_executor.execute(prompt_mut, prompt_id, {"client_id": self._server_stub.client_id}, + execute_outputs=validation_tuple[2]) + if self._prompt_executor.success: + return self._prompt_executor.outputs_ui + else: + raise RuntimeError("\n".join(self._prompt_executor.status_messages)) + + return await self._loop.run_in_executor(self._executor, execute_prompt) diff --git a/comfy/client/sdxl_with_refiner_workflow.py b/comfy/client/sdxl_with_refiner_workflow.py new file mode 100644 index 000000000..60864c766 --- /dev/null +++ b/comfy/client/sdxl_with_refiner_workflow.py @@ -0,0 +1,188 @@ +import copy +from typing import TypeAlias + +from comfy.api.components.schema.prompt import PromptDict, Prompt + +JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None +_BASE_PROMPT: JSON = { + "4": { + "inputs": { + "ckpt_name": "sd_xl_base_1.0.safetensors" + }, + "class_type": "CheckpointLoaderSimple" + }, + "5": { + "inputs": { + "width": 1024, + "height": 1024, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage" + }, + "6": { + "inputs": { + "text": "a photo of a cat", + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "10": { + "inputs": { + "add_noise": "enable", + "noise_seed": 42, + "steps": 20, + "cfg": 7.5, + "sampler_name": "euler", + "scheduler": "normal", + "start_at_step": 0, + "end_at_step": 32, + "return_with_leftover_noise": "enable", + "model": [ + "4", + 0 + ], + "positive": [ + "6", + 0 + ], + "negative": [ + "15", + 0 + ], + "latent_image": [ + "5", + 0 + ] + }, + "class_type": "KSamplerAdvanced" + }, + "12": { + "inputs": { + "samples": [ + "14", + 0 + ], + "vae": [ + "4", + 2 + ] + }, + "class_type": "VAEDecode" + }, + "13": { + "inputs": { + "filename_prefix": "test_inference", + "images": [ + "12", + 0 + ] + }, + "class_type": "SaveImage" + }, + "14": { + "inputs": { + "add_noise": "disable", + "noise_seed": 42, + "steps": 20, + "cfg": 7.5, + "sampler_name": "euler", + "scheduler": "normal", + "start_at_step": 32, + "end_at_step": 10000, + "return_with_leftover_noise": "disable", + "model": [ + "16", + 0 + ], + "positive": [ + "17", + 0 + ], + "negative": [ + "20", + 0 + ], + "latent_image": [ + "10", + 0 + ] + }, + "class_type": "KSamplerAdvanced" + }, + "15": { + "inputs": { + "conditioning": [ + "6", + 0 + ] + }, + "class_type": "ConditioningZeroOut" + }, + "16": { + "inputs": { + "ckpt_name": "sd_xl_refiner_1.0.safetensors" + }, + "class_type": "CheckpointLoaderSimple" + }, + "17": { + "inputs": { + "text": "a photo of a cat", + "clip": [ + "16", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "20": { + "inputs": { + "text": "", + "clip": [ + "16", + 1 + ] + }, + "class_type": "CLIPTextEncode" + } +} + + +def sdxl_workflow_with_refiner(prompt: str, + negative_prompt: str = "", + inference_steps=25, + refiner_steps=5, + sdxl_base_checkpoint_name="sd_xl_base_1.0.safetensors", + sdxl_refiner_checkpoint_name="sd_xl_refiner_1.0.safetensors", + width=1024, + height=1024, + sampler="euler_ancestral", + scheduler="normal", + filename_prefix="sdxl_") -> PromptDict: + prompt_dict: JSON = copy.deepcopy(_BASE_PROMPT) + prompt_dict["17"]["inputs"]["text"] = prompt + prompt_dict["20"]["inputs"]["text"] = negative_prompt + prompt_dict["16"]["inputs"]["ckpt_name"] = sdxl_refiner_checkpoint_name + prompt_dict["4"]["inputs"]["ckpt_name"] = sdxl_base_checkpoint_name + prompt_dict["5"]["inputs"]["width"] = width + prompt_dict["5"]["inputs"]["height"] = height + + # base + prompt_dict["10"]["inputs"]["steps"] = inference_steps + refiner_steps + prompt_dict["10"]["inputs"]["start_at_step"] = 0 + prompt_dict["10"]["inputs"]["end_at_step"] = inference_steps + prompt_dict["10"]["inputs"]["steps"] = inference_steps + refiner_steps + prompt_dict["10"]["inputs"]["sampler_name"] = sampler + prompt_dict["10"]["inputs"]["scheduler"] = scheduler + + # refiner + prompt_dict["14"]["inputs"]["steps"] = inference_steps + refiner_steps + prompt_dict["14"]["inputs"]["start_at_step"] = inference_steps + prompt_dict["14"]["inputs"]["end_at_step"] = inference_steps + refiner_steps + prompt_dict["14"]["inputs"]["sampler_name"] = sampler + prompt_dict["14"]["inputs"]["scheduler"] = scheduler + + prompt_dict["13"]["inputs"]["filename_prefix"] = filename_prefix + return Prompt.validate(prompt_dict) diff --git a/comfy/cmd/execution.py b/comfy/cmd/execution.py index 4b62a14e8..0c5ee0c70 100644 --- a/comfy/cmd/execution.py +++ b/comfy/cmd/execution.py @@ -1,74 +1,33 @@ from __future__ import annotations -import asyncio -import sys import copy -import datetime import heapq -import json +import inspect import logging +import sys import threading -import time import traceback import typing -from dataclasses import dataclass -from typing import Tuple -import sys -import gc -import inspect -from typing import List, Literal, NamedTuple, Optional +from typing import List, Optional, Tuple import torch +from ..component_model.abstract_prompt_queue import AbstractPromptQueue +from ..component_model.queue_types import QueueTuple, HistoryEntry, QueueItem, MAXIMUM_HISTORY_SIZE, ExecutionStatus +from ..component_model.executor_types import ExecutorToClientProgress from ..nodes.package import import_all_nodes_in_workspace + nodes = import_all_nodes_in_workspace() from .. import model_management # type: ignore -""" -A queued item -""" -QueueTuple = Tuple[float, int | str, dict, dict, list] - -def get_queue_priority(t: QueueTuple): - return t[0] - - -def get_prompt_id(t: QueueTuple): - return t[1] - - -def get_prompt(t: QueueTuple): - return t[2] - - -def get_extra_data(t: QueueTuple): - return t[3] - - -def get_good_outputs(t: QueueTuple): - return t[4] - - -class HistoryEntry(typing.TypedDict): - prompt: QueueTuple - outputs: dict - timestamp: int - - -@dataclass -class QueueItem: - """ - An item awaiting processing in the queue - """ - queue_tuple: QueueTuple - completed: asyncio.Future | None - - def __lt__(self, other: QueueItem): - return get_queue_priority(self.queue_tuple) < get_queue_priority(other.queue_tuple) - - -def get_input_data(inputs, class_def, unique_id, outputs={}, prompt={}, extra_data={}): +def get_input_data(inputs, class_def, unique_id, outputs=None, prompt=None, extra_data=None): + if extra_data is None: + extra_data = {} + if prompt is None: + prompt = {} + if outputs is None: + outputs = {} valid_inputs = class_def.INPUT_TYPES() input_data_all = {} for x in inputs: @@ -109,6 +68,7 @@ def map_node_over_list(obj, input_data_all, func, allow_interrupt=False): max_len_input = 0 else: max_len_input = max([len(x) for x in input_data_all.values()]) + # get a slice of inputs, repeat last input when list isn't long enough def slice_dict(d, i): d_new = dict() @@ -175,7 +135,9 @@ def format_value(x): else: return str(x) -def recursive_execute(server, prompt, outputs, current_item, extra_data, executed, prompt_id, outputs_ui, object_storage): + +def recursive_execute(server, prompt, outputs, current_item, extra_data, executed, prompt_id, outputs_ui, + object_storage): unique_id = current_item inputs = prompt[unique_id]['inputs'] class_type = prompt[unique_id]['class_type'] @@ -190,7 +152,8 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute input_unique_id = input_data[0] output_index = input_data[1] if input_unique_id not in outputs: - result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage) + result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, + outputs_ui, object_storage) if result[0] is not True: # Another node failed further upstream return result @@ -213,7 +176,7 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute outputs_ui[unique_id] = output_ui if server.client_id is not None: server.send_sync("executed", {"node": unique_id, "output": output_ui, "prompt_id": prompt_id}, - server.client_id) + server.client_id) except model_management.InterruptProcessingException as iex: logging.info("Processing interrupted") @@ -253,6 +216,7 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute return (True, None, None) + def recursive_will_execute(prompt, outputs, current_item): unique_id = current_item inputs = prompt[unique_id]['inputs'] @@ -326,7 +290,7 @@ def recursive_output_delete_if_changed(prompt, old_prompt, outputs, current_item class PromptExecutor: - def __init__(self, server): + def __init__(self, server: ExecutorToClientProgress): self.success = None self.server = server self.reset() @@ -372,7 +336,7 @@ class PromptExecutor: "current_outputs": error["current_outputs"], } self.add_message("execution_error", mes, broadcast=False) - + # Next, remove the subsequent outputs since they will not be executed to_delete = [] for o in self.outputs: @@ -385,7 +349,11 @@ class PromptExecutor: d = self.outputs.pop(o) del d - def execute(self, prompt, prompt_id, extra_data={}, execute_outputs=[]): + def execute(self, prompt, prompt_id, extra_data=None, execute_outputs: List[str] = None): + if execute_outputs is None: + execute_outputs = [] + if extra_data is None: + extra_data = {} model_management.interrupt_current_processing(False) if "client_id" in extra_data: @@ -428,8 +396,8 @@ class PromptExecutor: model_management.cleanup_models() self.add_message("execution_cached", - {"nodes": list(current_outputs), "prompt_id": prompt_id}, - broadcast=False) + {"nodes": list(current_outputs), "prompt_id": prompt_id}, + broadcast=False) executed = set() output_node_id = None to_execute = [] @@ -440,13 +408,16 @@ class PromptExecutor: while len(to_execute) > 0: # always execute the output that depends on the least amount of unexecuted nodes first to_execute = sorted(list( - map(lambda a: (len(recursive_will_execute(prompt, self.outputs, a[-1])), a[-1]), to_execute))) + map(lambda a: (len(recursive_will_execute(prompt, self.outputs, a[-1])), a[-1]), to_execute))) output_node_id = to_execute.pop(0)[-1] # This call shouldn't raise anything if there's an error deep in # the actual SD code, instead it will report the node where the # error was raised - self.success, error, ex = recursive_execute(self.server, prompt, self.outputs, output_node_id, extra_data, executed, prompt_id, self.outputs_ui, self.object_storage) + # todo: if we're using a distributed queue, we must wrap the server instance to correctly communicate back to the client via the exchange + self.success, error, ex = recursive_execute(self.server, prompt, self.outputs, output_node_id, + extra_data, executed, prompt_id, self.outputs_ui, + self.object_storage) if self.success is not True: self.handle_execution_error(prompt_id, prompt, current_outputs, executed, error, ex) break @@ -458,7 +429,6 @@ class PromptExecutor: model_management.unload_all_models() - def validate_inputs(prompt, item, validated) -> Tuple[bool, typing.List[dict], typing.Any]: # todo: this should check if LoadImage / LoadImageMask paths exist # todo: or, nodes should provide a way to validate their values @@ -647,7 +617,7 @@ def validate_inputs(prompt, item, validated) -> Tuple[bool, typing.List[dict], t if x in validate_function_inputs: input_filtered[x] = input_data_all[x] - #ret = obj_class.VALIDATE_INPUTS(**input_filtered) + # ret = obj_class.VALIDATE_INPUTS(**input_filtered) ret = map_node_over_list(obj_class, input_filtered, "VALIDATE_INPUTS") for x in input_filtered: for i, r in enumerate(ret): @@ -684,7 +654,23 @@ def full_type_name(klass): return klass.__qualname__ return module + '.' + klass.__qualname__ -def validate_prompt(prompt: dict) -> typing.Tuple[bool, dict | typing.List[dict] | None, typing.List[str], dict | list]: + +class ValidationErrorExtraInfoDict(typing.TypedDict): + exception_type: str + traceback: List[str] + + +class ValidationErrorDict(typing.TypedDict): + type: str + message: str + details: str + extra_info: ValidationErrorExtraInfoDict | dict + + +ValidationTuple = typing.Tuple[bool, ValidationErrorDict | None, typing.List[str], dict | list] + + +def validate_prompt(prompt: typing.Mapping[str, typing.Any]) -> ValidationTuple: outputs = set() for x in prompt: class_ = nodes.NODE_CLASS_MAPPINGS[prompt[x]['class_type']] @@ -698,7 +684,7 @@ def validate_prompt(prompt: dict) -> typing.Tuple[bool, dict | typing.List[dict] "details": "", "extra_info": {} } - return (False, error, [], []) + return False, error, [], [] good_outputs = set() errors = [] @@ -769,26 +755,22 @@ def validate_prompt(prompt: dict) -> typing.Tuple[bool, dict | typing.List[dict] "extra_info": {} } - return (False, error, list(good_outputs), node_errors) + return False, error, list(good_outputs), node_errors - return (True, None, list(good_outputs), node_errors) + return True, None, list(good_outputs), node_errors -MAXIMUM_HISTORY_SIZE = 10000 -class PromptQueue: - queue: typing.List[QueueItem] - currently_running: typing.Dict[int, QueueItem] - # history maps the second integer prompt id in the queue tuple to a dictionary with keys "prompt" and "outputs - history: typing.Dict[int, HistoryEntry] - - def __init__(self, server): +class PromptQueue(AbstractPromptQueue): + def __init__(self, server: ExecutorToClientProgress): self.server = server self.mutex = threading.RLock() self.not_empty = threading.Condition(self.mutex) self.next_task_id = 0 - self.queue = [] - self.currently_running = {} - self.history = {} + self.queue: typing.List[QueueItem] = [] + self.currently_running: typing.Dict[int, QueueItem] = {} + # history maps the second integer prompt id in the queue tuple to a dictionary with keys "prompt" and "outputs + # todo: use the new History class for the sake of simplicity + self.history: typing.Dict[str, HistoryEntry] = {} self.flags = {} server.prompt_queue = self @@ -814,13 +796,8 @@ class PromptQueue: self.server.queue_updated() return copy.deepcopy(item_with_future.queue_tuple), task_id - class ExecutionStatus(NamedTuple): - status_str: Literal['success', 'error'] - completed: bool - messages: List[str] - def task_done(self, item_id, outputs: dict, - status: Optional['PromptQueue.ExecutionStatus']): + status: Optional[ExecutionStatus]): with self.mutex: queue_item = self.currently_running.pop(item_id) prompt = queue_item.queue_tuple @@ -841,10 +818,6 @@ class PromptQueue: queue_item.completed.set_result(outputs) def get_current_queue(self) -> Tuple[typing.List[QueueTuple], typing.List[QueueTuple]]: - """ - Gets the current state of the queue - :return: A tuple containing (the currently running items, the items awaiting execution) - """ with self.mutex: out: typing.List[QueueTuple] = [] for x in self.currently_running.values(): @@ -866,7 +839,7 @@ class PromptQueue: def delete_queue_item(self, function): with self.mutex: for x in range(len(self.queue)): - if function(self.queue[x]): + if function(self.queue[x].queue_tuple): if len(self.queue) == 1: self.wipe_queue() else: @@ -899,9 +872,9 @@ class PromptQueue: def wipe_history(self): with self.mutex: - self.history = {} + self.history.clear() - def delete_history_item(self, id_to_delete: int): + def delete_history_item(self, id_to_delete: str): with self.mutex: self.history.pop(id_to_delete, None) diff --git a/comfy/cmd/latent_preview.py b/comfy/cmd/latent_preview.py index fedd560b7..a4a1c0a90 100644 --- a/comfy/cmd/latent_preview.py +++ b/comfy/cmd/latent_preview.py @@ -1,7 +1,8 @@ import torch from PIL import Image import numpy as np -from ..cli_args import args, LatentPreviewMethod +from ..cli_args import args +from ..cli_args_types import LatentPreviewMethod from ..taesd.taesd import TAESD from ..cmd import folder_paths from .. import utils diff --git a/comfy/cmd/main.py b/comfy/cmd/main.py index 3d3d89f81..9f66ee8de 100644 --- a/comfy/cmd/main.py +++ b/comfy/cmd/main.py @@ -7,6 +7,7 @@ import importlib.util from ..cmd import cuda_malloc from ..cmd import folder_paths +from ..analytics.analytics import initialize_event_tracking import time @@ -78,11 +79,12 @@ import yaml from ..cmd import execution from ..cmd import server as server_module -from .server import BinaryEventTypes +from ..component_model.abstract_prompt_queue import AbstractPromptQueue +from ..component_model.queue_types import BinaryEventTypes, ExecutionStatus from .. import model_management -def prompt_worker(q, _server): +def prompt_worker(q: AbstractPromptQueue, _server: server_module.PromptServer): e = execution.PromptExecutor(_server) last_gc_collect = 0 need_gc = False @@ -104,7 +106,7 @@ def prompt_worker(q, _server): need_gc = True q.task_done(item_id, e.outputs_ui, - status=execution.PromptQueue.ExecutionStatus( + status=ExecutionStatus( status_str='success' if e.success else 'error', completed=e.success, messages=e.status_messages)) @@ -226,6 +228,9 @@ def main(): threading.Thread(target=prompt_worker, daemon=True, args=(q, server,)).start() + # server has been imported and things should be looking good + initialize_event_tracking(loop) + if args.output_directory: output_dir = os.path.abspath(args.output_directory) print(f"Setting output directory to: {output_dir}") @@ -248,7 +253,7 @@ def main(): if args.auto_launch: def startup_server(address, port): import webbrowser - if os.name == 'nt' and address == '0.0.0.0': + if os.name == 'nt' and address == '0.0.0.0' or address == '': address = '127.0.0.1' webbrowser.open(f"http://{address}:{port}") diff --git a/comfy/cmd/openapi_gen.py b/comfy/cmd/openapi_gen.py index 795262cc6..aa4fd05c9 100644 --- a/comfy/cmd/openapi_gen.py +++ b/comfy/cmd/openapi_gen.py @@ -8,8 +8,9 @@ from importlib_resources import files, as_file from ..vendor.appdirs import user_cache_dir -_openapi_jar_basename = "openapi-generator-cli-6.6.0.jar" -_openapi_jar_url = f"https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.6.0/{_openapi_jar_basename}" +_version = "7.2.0" +_openapi_jar_basename = f"openapi-generator-cli-{_version}.jar" +_openapi_jar_url = f"https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/{_version}/{_openapi_jar_basename}" def is_java_installed(): @@ -41,12 +42,13 @@ def main(): "--add-opens", "java.base/java.io=ALL-UNNAMED", "--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.lang=ALL-UNNAMED", - "-jar", jar, + "-jar", str(jar), "generate", - "--input-spec", openapi_schema, + "--input-spec", str(openapi_schema).replace('\\', '/'), "--global-property", "models", - "--config", python_config + "--config", str(python_config).replace('\\', '/') ] + print(" ".join(cmds), file=sys.stderr) subprocess.check_output(cmds) diff --git a/comfy/cmd/server.py b/comfy/cmd/server.py index eea89cc5f..64fd8bd00 100644 --- a/comfy/cmd/server.py +++ b/comfy/cmd/server.py @@ -22,6 +22,7 @@ import aiofiles import aiohttp from aiohttp import web +from ..component_model.queue_types import QueueItem, HistoryEntry, BinaryEventTypes from ..cmd import execution from ..cmd import folder_paths import mimetypes @@ -30,6 +31,8 @@ from ..digest import digest from ..cli_args import args from .. import utils from .. import model_management +from ..component_model.executor_types import ExecutorToClientProgress +from ..component_model.file_output_path import file_output_path from ..nodes.package import import_all_nodes_in_workspace from ..vendor.appdirs import user_data_dir @@ -37,10 +40,6 @@ nodes = import_all_nodes_in_workspace() from ..app.user_manager import UserManager -class BinaryEventTypes: - PREVIEW_IMAGE = 1 - UNENCODED_PREVIEW_IMAGE = 2 - async def send_socket_catch_exception(function, message): try: @@ -75,16 +74,8 @@ def create_cors_middleware(allowed_origin: str): return cors_middleware -class PromptServer(): - prompt_queue: execution.PromptQueue | None - address: str - port: int - loop: AbstractEventLoop - messages: asyncio.Queue - number: int - supports: List[str] - app: web.Application - routes: web.RouteTableDef +class PromptServer(ExecutorToClientProgress): + instance: 'PromptServer' def __init__(self, loop): PromptServer.instance = self @@ -92,19 +83,22 @@ class PromptServer(): mimetypes.init() mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8' + self.address: str = "0.0.0.0" self.user_manager = UserManager() - self.supports = ["custom_nodes_from_web"] - self.prompt_queue = None - self.loop = loop - self.messages = asyncio.Queue() - self.number = 0 + # todo: this is probably read by custom nodes elsewhere + self.supports: List[str] = ["custom_nodes_from_web"] + self.prompt_queue: execution.AbstractPromptQueue | None = None + self.loop: AbstractEventLoop = loop + self.messages: asyncio.Queue = asyncio.Queue() + self.number: int = 0 + self.port: int = 8188 middlewares = [cache_control] if args.enable_cors_header: middlewares.append(create_cors_middleware(args.enable_cors_header)) max_upload_size = round(args.max_upload_size * 1024 * 1024) - self.app = web.Application(client_max_size=max_upload_size, handler_args={'max_field_size': 16380}, + self.app: web.Application = web.Application(client_max_size=max_upload_size, handler_args={'max_field_size': 16380}, middlewares=middlewares) self.sockets = dict() web_root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../web") @@ -112,7 +106,7 @@ class PromptServer(): web_root_path = resource_filename('comfy', 'web/') self.web_root = web_root_path routes = web.RouteTableDef() - self.routes = routes + self.routes: web.RouteTableDef = routes self.last_node_id = None self.client_id = None @@ -276,28 +270,16 @@ class PromptServer(): async def view_image(request): if "filename" in request.rel_url.query: filename = request.rel_url.query["filename"] - filename, output_dir = folder_paths.annotated_filepath(filename) + type = request.rel_url.query.get("type", "output") + subfolder = request.rel_url.query["subfolder"] if "subfolder" in request.rel_url.query else None - # validation for security: prevent accessing arbitrary path - if filename[0] == '/' or '..' in filename: + try: + file = file_output_path(filename, type=type, subfolder=subfolder) + except PermissionError: + return web.Response(status=403) + except ValueError: return web.Response(status=400) - if output_dir is None: - type = request.rel_url.query.get("type", "output") - output_dir = folder_paths.get_directory_by_type(type) - - if output_dir is None: - return web.Response(status=400) - - if "subfolder" in request.rel_url.query: - full_output_dir = os.path.join(output_dir, request.rel_url.query["subfolder"]) - if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir: - return web.Response(status=403) - output_dir = full_output_dir - - filename = os.path.basename(filename) - file = os.path.join(output_dir, filename) - if os.path.isfile(file): if 'preview' in request.rel_url.query: with Image.open(file) as img: @@ -505,8 +487,8 @@ class PromptServer(): prompt_id = str(uuid.uuid4()) outputs_to_execute = valid[2] self.prompt_queue.put( - execution.QueueItem(queue_tuple=(number, prompt_id, prompt, extra_data, outputs_to_execute), - completed=None)) + QueueItem(queue_tuple=(number, prompt_id, prompt, extra_data, outputs_to_execute), + completed=None)) response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]} return web.json_response(response) else: @@ -561,13 +543,14 @@ class PromptServer(): @routes.get("/api/v1/images/{content_digest}") async def get_image(request: web.Request) -> web.FileResponse: digest_ = request.match_info['content_digest'] - path = os.path.join(user_data_dir("comfyui", "comfyanonymous", roaming=False), digest_) + path = str(os.path.join(user_data_dir("comfyui", "comfyanonymous", roaming=False), digest_)) return web.FileResponse(path, headers={"Content-Disposition": f"filename=\"{digest_}.png\""}) @routes.post("/api/v1/prompts") async def post_prompt(request: web.Request) -> web.Response | web.FileResponse: # check if the queue is too long + accept = request.headers.get("accept", "application/json") queue_size = self.prompt_queue.size() queue_too_busy_size = PromptServer.get_too_busy_queue_size() if queue_size > queue_too_busy_size: @@ -611,12 +594,22 @@ class PromptServer(): cache_url = f"/api/v1/images/{content_digest}" if os.path.exists(cache_path): - return web.Response(status=200, - headers={ - "Digest": f"SHA-256={content_digest}", - "Location": f"/api/v1/images/{content_digest}" - }, - body=json.dumps({'urls': [cache_url]})) + filename__ = os.path.basename(cache_path) + digest_headers_ = { + "Digest": f"SHA-256={content_digest}", + "Location": f"/api/v1/images/{content_digest}" + } + if accept == "application/json": + + return web.Response(status=200, + headers=digest_headers_, + body=json.dumps({'urls': [cache_url]})) + elif accept == "image/png": + return web.FileResponse(cache_path, + headers={"Content-Disposition": f"filename=\"{filename__}\"", + **digest_headers_}) + else: + return web.json_response(status=400, reason=f"invalid accept header {accept}") # todo: check that the files specified in the InputFile nodes exist @@ -625,8 +618,8 @@ class PromptServer(): number = self.number self.number += 1 self.prompt_queue.put( - execution.QueueItem(queue_tuple=(number, str(uuid.uuid4()), prompt_dict, {}, valid[2]), - completed=completed)) + QueueItem(queue_tuple=(number, str(uuid.uuid4()), prompt_dict, {}, valid[2]), + completed=completed)) try: await completed @@ -641,8 +634,9 @@ class PromptServer(): images: List[dict] = [] if 'images' in node: images = node['images'] - elif isinstance(node, dict) and 'ui' in node and isinstance(node['ui'], dict) and 'images' in node[ - 'ui']: + elif (isinstance(node, dict) + and 'ui' in node and isinstance(node['ui'], dict) + and 'images' in node['ui']): images = node['ui']['images'] for image_tuple in images: filename_ = image_tuple['abs_path'] @@ -658,12 +652,19 @@ class PromptServer(): shutil.copy(image_, cache_path) filename = os.path.basename(image_) comfyui_url = f"http://{self.address}:{self.port}/view?filename={filename}&type=output" - return web.Response(status=200, - headers={ - "Digest": f"SHA-256={content_digest}", - "Location": f"/api/v1/images/{content_digest}", - "Content-Disposition": f"filename=\"{filename}\""}, - body=json.dumps({'urls': [cache_url, comfyui_url]})) + digest_headers_ = { + "Digest": f"SHA-256={content_digest}", + "Location": f"/api/v1/images/{content_digest}", + "Content-Disposition": f"filename=\"{filename}\"" + } + if accept == "application/json": + + return web.Response(status=200, + headers=digest_headers_, + body=json.dumps({'urls': [cache_url, comfyui_url]})) + elif accept == "image/png": + return web.FileResponse(image_, + headers=digest_headers_) else: return web.Response(status=204) @@ -674,12 +675,7 @@ class PromptServer(): if len(history_items) == 0: return web.Response(status=404) - # argmax - def _history_item_timestamp(i: int): - return history_items[i]['timestamp'] - - last_history_item: execution.HistoryEntry = history_items[ - max(range(len(history_items)), key=_history_item_timestamp)] + last_history_item: HistoryEntry = history_items[-1] prompt = last_history_item['prompt'][2] return web.json_response(prompt, status=200) diff --git a/comfy/component_model/__init__.py b/comfy/component_model/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/component_model/abstract_prompt_queue.py b/comfy/component_model/abstract_prompt_queue.py new file mode 100644 index 000000000..080d102dc --- /dev/null +++ b/comfy/component_model/abstract_prompt_queue.py @@ -0,0 +1,112 @@ +import typing +from abc import ABCMeta, abstractmethod + +from .queue_types import QueueTuple, HistoryEntry, QueueItem, Flags, ExecutionStatus + + +class AbstractPromptQueue(metaclass=ABCMeta): + """ + The interface of a queue inside ComfyUI. + + put is intended to be used by a prompt creator. + + get is intended to be used by a worker. + """ + @abstractmethod + def size(self) -> int: + """ + The number of items currently in the queue. Excludes items being processed. + :return: + """ + pass + + @abstractmethod + def put(self, item: QueueItem): + """ + Puts an item on the queue. + :param item: a queue item + :return: + """ + pass + + @abstractmethod + def get(self, timeout: float | None = None) -> typing.Optional[typing.Tuple[QueueTuple, int]]: + """ + Pops an item off the queue. Blocking. If a timeout is provided, this will return None after + :param timeout: the number of seconds to time out for a blocking get + :return: the queue tuple and its item ID, or None if timed out or no item is on the queue + """ + pass + + @abstractmethod + def task_done(self, item_id: int, outputs: dict, + status: typing.Optional[ExecutionStatus]): + """ + Signals to the user interface that the task with the specified id is completed + :param item_id: the ID of the task that should be marked as completed + :param outputs: an opaque dictionary of outputs + :param status: + :return: + """ + pass + + @abstractmethod + def get_current_queue(self) -> typing.Tuple[typing.List[QueueTuple], typing.List[QueueTuple]]: + """ + Gets the current state of the queue + :return: A tuple containing (the currently running items, the items awaiting execution) + """ + pass + + @abstractmethod + def get_tasks_remaining(self) -> int: + """ + Gets the length of the queue and the currently processing tasks + :return: an integer count + """ + pass + + @abstractmethod + def wipe_queue(self) -> None: + """ + Deletes all items on the queue + :return: + """ + pass + + @abstractmethod + def delete_queue_item(self, function: typing.Callable[[QueueTuple], bool]) -> bool: + """ + Deletes the first queue item that satisfies the predicate + :param function: a predicate that takes queue tuples and returns true if it matches + :return: True if an item as removed + """ + pass + + @abstractmethod + def get_history(self, prompt_id: typing.Optional[str] = None, max_items=None, offset=-1) -> typing.Mapping[ + str, HistoryEntry]: + """ + Creates a deep copy of the history + :param prompt_id: + :param max_items: + :param offset: + :return: + """ + pass + + @abstractmethod + def wipe_history(self): + pass + + @abstractmethod + def delete_history_item(self, id_to_delete: str): + pass + + @abstractmethod + def set_flag(self, name: str, data: bool) -> None: + pass + + @abstractmethod + def get_flags(self, reset) -> Flags: + pass diff --git a/comfy/component_model/executor_types.py b/comfy/component_model/executor_types.py new file mode 100644 index 000000000..79264e383 --- /dev/null +++ b/comfy/component_model/executor_types.py @@ -0,0 +1,56 @@ +from typing import Optional, Literal, Protocol, TypedDict, NotRequired + +from comfy.component_model.queue_types import BinaryEventTypes + + +class ExecInfo(TypedDict): + queue_remaining: int + + +class QueueInfo(TypedDict): + exec_info: ExecInfo + + +class StatusMessage(TypedDict): + status: QueueInfo + sid: NotRequired[str] + + +class ExecutingMessage(TypedDict): + node: str | None + prompt_id: NotRequired[str] + + +class ExecutorToClientProgress(Protocol): + """ + Specifies the interface for the dependencies a prompt executor needs from a server. + + Attributes: + client_id (Optional[str]): the client ID that this object collects feedback for + last_node_id: (Optional[str]): the most recent node that was processed by the executor + last_prompt_id: (Optional[str]): the most recent prompt that was processed by the executor + """ + + client_id: Optional[str] + last_node_id: Optional[str] + last_prompt_id: Optional[str] + + def send_sync(self, + event: Literal["status", "executing"] | BinaryEventTypes | str | None, + data: StatusMessage | ExecutingMessage | bytes | bytearray | None, sid: str | None = None): + """ + Sends feedback to the client with the specified ID about a specific node + + :param event: a string event name, BinaryEventTypes.UNENCODED_PREVIEW_IMAGE, BinaryEventTypes.PREVIEW_IMAGE, 0 (?) or None + :param data: a StatusMessage dict when the event is status; an ExecutingMessage dict when the status is executing, binary bytes with a binary event type, or nothing + :param sid: websocket ID / the client ID to be responding to + :return: + """ + pass + + def queue_updated(self): + """ + Indicates that the local client's queue has been updated + :return: + """ + pass diff --git a/comfy/component_model/file_output_path.py b/comfy/component_model/file_output_path.py new file mode 100644 index 000000000..12701f4f5 --- /dev/null +++ b/comfy/component_model/file_output_path.py @@ -0,0 +1,40 @@ +import os +from typing import Literal, Optional +from pathlib import Path + +from ..cmd import folder_paths + + +def _is_strictly_below_root(path: Path) -> bool: + resolved_path = path.resolve() + return ".." not in resolved_path.parts and resolved_path.is_absolute() + + +def file_output_path(filename: str, type: Literal["input", "output", "temp"] = "output", + subfolder: Optional[str] = None) -> str: + """ + Takes the contents of a file output node and returns an actual path to the file referenced in it. + + This is adapted from the /view code + :param filename: + :param type: + :param subfolder: + :return: + """ + filename, output_dir = folder_paths.annotated_filepath(str(filename)) + if not _is_strictly_below_root(Path(filename)): + raise PermissionError("insecure") + + if output_dir is None: + output_dir = folder_paths.get_directory_by_type(type) + if output_dir is None: + raise ValueError(f"no such output directory because invalid type specified (type={type})") + if subfolder is not None: + full_output_dir = os.path.join(output_dir, subfolder) + if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir: + raise PermissionError("insecure") + output_dir = full_output_dir + + filename = os.path.basename(filename) + file = os.path.join(output_dir, filename) + return file diff --git a/comfy/component_model/make_mutable.py b/comfy/component_model/make_mutable.py new file mode 100644 index 000000000..01f7b1b5f --- /dev/null +++ b/comfy/component_model/make_mutable.py @@ -0,0 +1,20 @@ +from typing import Mapping, Any + + +def make_mutable(obj: Any) -> dict: + """ + Makes an immutable dict, frozenset or tuple mutable. Otherwise, returns the value. + :param obj: any object + :return: + """ + if isinstance(obj, Mapping) and not isinstance(obj, dict) and not hasattr(obj, "__setitem__"): + obj = dict(obj) + for key, value in obj.items(): + obj[key] = make_mutable(value) + if isinstance(obj, tuple): + obj = list(obj) + for i in range(len(obj)): + obj[i] = make_mutable(obj[i]) + if isinstance(obj, frozenset): + obj = set([make_mutable(x) for x in obj]) + return obj diff --git a/comfy/component_model/queue_types.py b/comfy/component_model/queue_types.py new file mode 100644 index 000000000..792dcd621 --- /dev/null +++ b/comfy/component_model/queue_types.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import asyncio +from enum import Enum +from typing import NamedTuple, Optional, TypedDict, List, Literal, NotRequired +from dataclasses import dataclass +from typing import Tuple + +QueueTuple = Tuple[float, str, dict, dict, list] +MAXIMUM_HISTORY_SIZE = 10000 + + +class TaskInvocation(NamedTuple): + item_id: int | str + outputs: dict + status: Optional[ExecutionStatus] + + +class ExecutionStatus(NamedTuple): + status_str: Literal['success', 'error'] + completed: bool + messages: List[str] + + +class ExecutionStatusAsDict(TypedDict): + status_str: Literal['success', 'error'] + completed: bool + messages: List[str] + + +class Flags(TypedDict, total=False): + unload_models: NotRequired[bool] + free_memory: NotRequired[bool] + + +class HistoryEntry(TypedDict): + prompt: QueueTuple + outputs: dict + status: NotRequired[ExecutionStatusAsDict] + + +@dataclass +class QueueItem: + """ + An item awaiting processing in the queue + + Attributes: + queue_tuple (QueueTuple): the corresponding queued workflow and other related data + completed (Optional[Future[TaskInvocation | dict]]): A future of a task invocation (the signature of the task_done method) + or a dictionary of outputs + """ + queue_tuple: QueueTuple + completed: asyncio.Future[TaskInvocation | dict] | None + + def __lt__(self, other: QueueItem): + return self.queue_tuple[0] < other.queue_tuple[0] + + @property + def priority(self) -> float: + return self.queue_tuple[0] + + @property + def prompt_id(self) -> str: + return self.queue_tuple[1] + + @property + def prompt(self) -> dict: + return self.queue_tuple[2] + + @property + def extra_data(self) -> Optional[dict]: + if len(self.queue_tuple) > 2: + return self.queue_tuple[3] + else: + return None + + @property + def good_outputs(self) -> Optional[List[str]]: + if len(self.queue_tuple) > 3: + return self.queue_tuple[4] + else: + return None + + +class BinaryEventTypes(Enum): + PREVIEW_IMAGE = 1 + UNENCODED_PREVIEW_IMAGE = 2 + + +class ExecutorToClientMessage(TypedDict, total=False): + node: str + prompt_id: str + output: NotRequired[str] diff --git a/comfy/distributed/__init__.py b/comfy/distributed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/comfy/distributed/distributed_prompt_queue.py b/comfy/distributed/distributed_prompt_queue.py new file mode 100644 index 000000000..d8f68cdf3 --- /dev/null +++ b/comfy/distributed/distributed_prompt_queue.py @@ -0,0 +1,198 @@ +import asyncio +from asyncio import AbstractEventLoop +from typing import Optional, Dict, List, Mapping, Tuple, Callable + +from aio_pika import connect_robust +from aio_pika.abc import AbstractConnection, AbstractChannel +from aio_pika.patterns import RPC + +from ..component_model.abstract_prompt_queue import AbstractPromptQueue +from ..component_model.executor_types import ExecutorToClientProgress +from ..component_model.queue_types import Flags, HistoryEntry, QueueTuple, QueueItem, ExecutionStatus, TaskInvocation +from .history import History +from ..cmd.server import PromptServer + + +class DistributedPromptQueue(AbstractPromptQueue): + """ + A distributed prompt queue for + """ + + def size(self) -> int: + """ + In a distributed queue, this only returns the client's apparent number of items it is waiting for + :return: + """ + return len(self.caller_local_in_progress) + + async def put_async(self, queue_item: QueueItem): + assert self.is_caller + self.caller_local_in_progress[queue_item.prompt_id] = queue_item + if self.caller_server is not None: + self.caller_server.queue_updated() + try: + res: TaskInvocation = await self.rpc.call(self.queue_name, {"item": queue_item.queue_tuple}) + + self.caller_history.put(queue_item, res.outputs, res.status) + if self.caller_server is not None: + self.caller_server.queue_updated() + + # if this has a completion future, complete it + if queue_item.completed is not None: + queue_item.completed.set_result(res) + return res + except Exception as e: + # if a caller-side error occurred, use the passed error for the messages + # we didn't receive any outputs here + self.caller_history.put(queue_item, outputs={}, + status=ExecutionStatus(status_str="error", completed=False, messages=[str(e)])) + + # if we have a completer, propoagate the exception to it + if queue_item.completed is not None: + queue_item.completed.set_exception(e) + else: + # otherwise, this should raise in the event loop, which I suppose isn't handled + raise e + finally: + self.caller_local_in_progress.pop(queue_item.prompt_id) + if self.caller_server is not None: + self.caller_server.queue_updated() + + def put(self, item: QueueItem): + # caller: execute on main thread + assert self.is_caller + # this is called by the web server and its event loop is perfectly fine to use + # the future is now ignored + self.loop.call_soon_threadsafe(self.put_async, item) + + async def _callee_do_work_item(self, item: QueueTuple) -> TaskInvocation: + assert self.is_callee + item_with_completer = QueueItem(item, self.loop.create_future()) + self.callee_local_in_progress[item_with_completer.prompt_id] = item_with_completer + # todo: check if we have the local model content needed to execute this request and if not, reject it + # todo: check if we have enough memory to execute this request, and if not, reject it + await self.callee_local_queue.put(item) + + # technically this could be messed with or overwritten + assert item_with_completer.completed is not None + assert not item_with_completer.completed.done() + + # now we wait for the worker thread to complete the item + return await item_with_completer.completed + + def get(self, timeout: float | None = None) -> Optional[Tuple[QueueTuple, int]]: + # callee: executed on the worker thread + assert self.is_callee + try: + item = asyncio.run_coroutine_threadsafe(self.callee_local_queue.get(), self.loop).result(timeout) + except TimeoutError: + return None + + return item, item[1] + + def task_done(self, item_id: int, outputs: dict, status: Optional[ExecutionStatus]): + # callee: executed on the worker thread + assert self.is_callee + pending = self.callee_local_in_progress.pop(item_id) + assert pending is not None + assert pending.completed is not None + assert not pending.completed.done() + # finish the task. status will transmit the errors in comfy's domain-specific way + pending.completed.set_result(TaskInvocation(item_id=item_id, outputs=outputs, status=status)) + + def get_current_queue(self) -> Tuple[List[QueueTuple], List[QueueTuple]]: + """ + In a distributed queue, all queue items are assumed to be currently in progress + :return: + """ + return [], [item.queue_tuple for item in self.caller_local_in_progress.values()] + + def get_tasks_remaining(self) -> int: + """ + In a distributed queue, shows only the items that this caller is currently waiting for + :return: + """ + # caller: executed on main thread + return len(self.caller_local_in_progress) + + def wipe_queue(self) -> None: + """ + Does nothing on distributed queues. Once an item has been sent, it cannot be cancelled. + :return: + """ + pass + + def delete_queue_item(self, function: Callable[[QueueTuple], bool]) -> bool: + """ + Does nothing on distributed queues. Once an item has been sent, it cannot be cancelled. + :param function: + :return: + """ + return False + + def get_history(self, prompt_id: Optional[int] = None, max_items=None, offset=-1) \ + -> Mapping[str, HistoryEntry]: + return self.caller_history.copy(prompt_id=prompt_id, max_items=max_items, offset=offset) + + def wipe_history(self): + self.caller_history.clear() + + def delete_history_item(self, id_to_delete): + self.caller_history.pop(id_to_delete) + + def set_flag(self, name: str, data: bool) -> None: + """ + Does nothing on distributed queues. Workers must manage their own memory. + :param name: + :param data: + :return: + """ + pass + + def get_flags(self, reset) -> Flags: + """ + Does nothing on distributed queues. Workers must manage their own memory. + :param reset: + :return: + """ + return Flags() + + def __init__(self, + server: Optional[ExecutorToClientProgress | PromptServer] = None, + queue_name: str = "comfyui", + connection_uri="amqp://localhost/", + is_caller=True, + is_callee=True, + loop: Optional[AbstractEventLoop] = None): + super().__init__() + # this constructor is called on the main thread + self.loop = loop or asyncio.get_event_loop() or asyncio.new_event_loop() + self.queue_name = queue_name + self.connection_uri = connection_uri + self.connection: Optional[AbstractConnection] = None # Connection will be set up asynchronously + self.channel: Optional[AbstractChannel] = None # Channel will be set up asynchronously + self.is_caller = is_caller + self.is_callee = is_callee + + # as rpc caller + self.caller_server = server + self.caller_local_in_progress: dict[str | int, QueueItem] = {} + self.caller_history: History = History() + + # as rpc callee + self.callee_local_queue = asyncio.Queue() + self.callee_local_in_progress: Dict[int | str, QueueItem] = {} + self.rpc: Optional[RPC] = None + + # todo: the prompt queue really shouldn't do this + if server is not None: + server.prompt_queue = self + + async def init(self): + self.connection = await connect_robust(self.connection_uri, loop=self.loop) + self.channel = await self.connection.channel() + self.rpc = await RPC.create(channel=self.channel) + self.rpc.host_exceptions = True + # this makes the queue available to complete work items + if self.is_callee: + await self.rpc.register(self.queue_name, self._callee_do_work_item) diff --git a/comfy/distributed/distributed_prompt_worker.py b/comfy/distributed/distributed_prompt_worker.py new file mode 100644 index 000000000..31bb42148 --- /dev/null +++ b/comfy/distributed/distributed_prompt_worker.py @@ -0,0 +1,50 @@ +import asyncio +from asyncio import AbstractEventLoop +from typing import Optional + +from aio_pika import connect_robust +from aio_pika.patterns import RPC + +from ..api.components.schema.prompt import Prompt +from ..cli_args_types import Configuration +from ..client.embedded_comfy_client import EmbeddedComfyClient +from ..component_model.queue_types import TaskInvocation, QueueTuple, QueueItem, ExecutionStatus + + +class DistributedPromptWorker: + """ + A work in progress distributed prompt worker. + """ + + def __init__(self, embedded_comfy_client: EmbeddedComfyClient, + connection_uri: str = "amqp://localhost:5672/", + queue_name: str = "comfyui", + loop: Optional[AbstractEventLoop] = None, configuration: Configuration = None): + self._queue_name = queue_name + self._configuration = configuration + self._connection_uri = connection_uri + self._loop = loop or asyncio.get_event_loop() + self._embedded_comfy_client = embedded_comfy_client + + async def _do_work_item(self, item: QueueTuple) -> TaskInvocation: + item_without_completer = QueueItem(item, completed=None) + try: + output_dict = await self._embedded_comfy_client.queue_prompt(Prompt.validate(item_without_completer.prompt)) + return TaskInvocation(item_without_completer.prompt_id, outputs=output_dict, + status=ExecutionStatus("success", True, [])) + except Exception as e: + return TaskInvocation(item_without_completer.prompt_id, outputs={}, + status=ExecutionStatus("error", False, [str(e)])) + + async def __aenter__(self) -> "DistributedPromptWorker": + self._connection = await connect_robust(self._connection_uri, loop=self._loop) + self._channel = await self._connection.channel() + self._rpc = await RPC.create(channel=self._channel) + self._rpc.host_exceptions = True + await self._rpc.register(self._queue_name, self._do_work_item) + return self + + async def __aexit__(self, *args): + await self._rpc.close() + await self._channel.close() + await self._connection.close() diff --git a/comfy/distributed/history.py b/comfy/distributed/history.py new file mode 100644 index 000000000..cc2e0a7a4 --- /dev/null +++ b/comfy/distributed/history.py @@ -0,0 +1,29 @@ +import copy +from typing import Optional, OrderedDict, List, Dict +import collections +from itertools import islice + +from comfy.component_model.queue_types import HistoryEntry, QueueItem, ExecutionStatus, MAXIMUM_HISTORY_SIZE + + +class History: + def __init__(self): + self.history: OrderedDict[str, HistoryEntry] = collections.OrderedDict() + + def put(self, queue_item: QueueItem, outputs: dict, status: ExecutionStatus): + self.history[queue_item.prompt_id] = HistoryEntry(prompt=queue_item.queue_tuple, + outputs=outputs, + status=status._asdict()) + + def copy(self, prompt_id: Optional[str | int] = None, max_items: int = MAXIMUM_HISTORY_SIZE, + offset: int = 0) -> Dict[str, HistoryEntry]: + if prompt_id in self.history: + return {prompt_id: copy.deepcopy(self.history[prompt_id])} + else: + return dict(islice(self.history, offset, max_items)) + + def clear(self): + self.history.clear() + + def pop(self, key: str): + self.history.pop(key) diff --git a/comfy/nodes/package_typing.py b/comfy/nodes/package_typing.py index 449750f6b..5f3be9f48 100644 --- a/comfy/nodes/package_typing.py +++ b/comfy/nodes/package_typing.py @@ -4,6 +4,8 @@ import typing from typing import Protocol, ClassVar, Tuple, Dict from dataclasses import dataclass, field +T = typing.TypeVar('T', bound='CustomNode') + class CustomNode(Protocol): @classmethod @@ -17,6 +19,9 @@ class CustomNode(Protocol): CATEGORY: ClassVar[str] OUTPUT_NODE: typing.Optional[ClassVar[bool]] + def __call__(self) -> T: + ... + @dataclass class ExportedNodes: diff --git a/comfy/sampler_names.py b/comfy/sampler_names.py new file mode 100644 index 000000000..122d345a9 --- /dev/null +++ b/comfy/sampler_names.py @@ -0,0 +1,5 @@ +KSAMPLER_NAMES = ["euler", "euler_ancestral", "heun", "heunpp2", "dpm_2", "dpm_2_ancestral", + "lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_sde", "dpmpp_sde_gpu", + "dpmpp_2m", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm"] +SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"] +SAMPLER_NAMES = KSAMPLER_NAMES + ["ddim", "uni_pc", "uni_pc_bh2"] diff --git a/comfy/samplers.py b/comfy/samplers.py index eeac3fbd5..ca9cc304f 100644 --- a/comfy/samplers.py +++ b/comfy/samplers.py @@ -5,6 +5,9 @@ import collections from . import model_management import math +from .sampler_names import SCHEDULER_NAMES, SAMPLER_NAMES + + def get_area_and_mult(conds, x_in, timestep_in): area = (x_in.shape[2], x_in.shape[3], 0, 0) strength = 1.0 @@ -521,9 +524,6 @@ class UNIPCBH2(Sampler): def sample(self, model_wrap, sigmas, extra_args, callback, noise, latent_image=None, denoise_mask=None, disable_pbar=False): return uni_pc.sample_unipc(model_wrap, noise, latent_image, sigmas, max_denoise=self.max_denoise(model_wrap, sigmas), extra_args=extra_args, noise_mask=denoise_mask, callback=callback, variant='bh2', disable=disable_pbar) -KSAMPLER_NAMES = ["euler", "euler_ancestral", "heun", "heunpp2","dpm_2", "dpm_2_ancestral", - "lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_sde", "dpmpp_sde_gpu", - "dpmpp_2m", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm"] class KSAMPLER(Sampler): def __init__(self, sampler_function, extra_options={}, inpaint_options={}): @@ -618,8 +618,6 @@ def sample(model, noise, positive, negative, cfg, device, sampler, sigmas, model samples = sampler.sample(model_wrap, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar) return model.process_latent_out(samples.to(torch.float32)) -SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"] -SAMPLER_NAMES = KSAMPLER_NAMES + ["ddim", "uni_pc", "uni_pc_bh2"] def calculate_sigmas_scheduler(model, scheduler_name, steps): if scheduler_name == "karras": diff --git a/comfy_extras/nodes/nodes_custom_sampler.py b/comfy_extras/nodes/nodes_custom_sampler.py index 39c7e28e6..adf975922 100644 --- a/comfy_extras/nodes/nodes_custom_sampler.py +++ b/comfy_extras/nodes/nodes_custom_sampler.py @@ -1,3 +1,4 @@ +import comfy.sampler_names from comfy import samplers from comfy import model_management from comfy import sample @@ -12,7 +13,7 @@ class BasicScheduler: def INPUT_TYPES(s): return {"required": {"model": ("MODEL",), - "scheduler": (samplers.SCHEDULER_NAMES, ), + "scheduler": (comfy.sampler_names.SCHEDULER_NAMES,), "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), } @@ -170,7 +171,7 @@ class KSamplerSelect: @classmethod def INPUT_TYPES(s): return {"required": - {"sampler_name": (samplers.SAMPLER_NAMES, ), + {"sampler_name": (comfy.sampler_names.SAMPLER_NAMES,), } } RETURN_TYPES = ("SAMPLER",) diff --git a/comfy_extras/nodes/nodes_stable3d.py b/comfy_extras/nodes/nodes_stable3d.py index 05c76926b..b8910581c 100644 --- a/comfy_extras/nodes/nodes_stable3d.py +++ b/comfy_extras/nodes/nodes_stable3d.py @@ -61,8 +61,8 @@ class StableZero123_Conditioning_Batched: return {"required": { "clip_vision": ("CLIP_VISION",), "init_image": ("IMAGE",), "vae": ("VAE",), - "width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), + "width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), "elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), "azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), diff --git a/requirements-dev.txt b/requirements-dev.txt index 51e6ffc1d..3da3d2571 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,6 @@ pytest +pytest-asyncio websocket-client==1.6.1 -PyInstaller \ No newline at end of file +PyInstaller +testcontainers-rabbitmq +mypy>=1.6.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a1d9d7a6b..ccd3abd49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,5 +28,5 @@ scipy tqdm protobuf==3.20.3 psutil -mypy>=1.6.0 -ConfigArgParse \ No newline at end of file +ConfigArgParse +aio-pika \ No newline at end of file diff --git a/tests/compare/conftest.py b/tests/compare/conftest.py index dd5078c9e..07f67dca0 100644 --- a/tests/compare/conftest.py +++ b/tests/compare/conftest.py @@ -9,7 +9,7 @@ def pytest_addoption(parser): parser.addoption('--img_output_dir', action="store", default='tests/compare/samples', help='Output directory for diff metric images') # This initializes args at the beginning of the test session -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session", autouse=False) def args_pytest(pytestconfig): args = {} args['baseline_dir'] = pytestconfig.getoption('baseline_dir') diff --git a/tests/compare/test_quality.py b/tests/compare/test_quality.py index 92a2d5a8b..5cb819dae 100644 --- a/tests/compare/test_quality.py +++ b/tests/compare/test_quality.py @@ -36,7 +36,7 @@ class TestCompareImageMetrics: yield fnames del fnames - @fixture(scope="class", autouse=True) + @fixture(scope="class") def teardown(self, args_pytest): yield # Runs after all tests are complete @@ -67,17 +67,17 @@ class TestCompareImageMetrics: # Tests run for each baseline file name @fixture() - def fname(self, baseline_fname): + def fname(self, baseline_fname, teardown): yield baseline_fname del baseline_fname - def test_directories_not_empty(self, args_pytest): + def test_directories_not_empty(self, args_pytest, teardown): baseline_dir = args_pytest['baseline_dir'] test_dir = args_pytest['test_dir'] assert len(os.listdir(baseline_dir)) != 0, f"Baseline directory {baseline_dir} is empty" assert len(os.listdir(test_dir)) != 0, f"Test directory {test_dir} is empty" - def test_dir_has_all_matching_metadata(self, fname, test_file_names, args_pytest): + def test_dir_has_all_matching_metadata(self, fname, test_file_names, args_pytest, teardown): # Check that all files in baseline_dir have a file in test_dir with matching metadata baseline_file_path = os.path.join(args_pytest['baseline_dir'], fname) file_paths = [os.path.join(args_pytest['test_dir'], f) for f in test_file_names] @@ -93,6 +93,7 @@ class TestCompareImageMetrics: fname, test_file_names, metric, + teardown, ): baseline_dir = args_pytest['baseline_dir'] test_dir = args_pytest['test_dir'] diff --git a/tests/conftest.py b/tests/conftest.py index 1a35880af..eadb6c16a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,31 @@ import os +import time +import urllib + import pytest + # Command line arguments for pytest def pytest_addoption(parser): - parser.addoption('--output_dir', action="store", default='tests/inference/samples', help='Output directory for generated images') - parser.addoption("--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0", help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)") + parser.addoption('--output_dir', action="store", default='tests/inference/samples', + help='Output directory for generated images') + parser.addoption("--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0", + help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)") parser.addoption("--port", type=int, default=8188, help="Set the listen port.") + +def run_server(args_pytest): + from comfy.cmd.main import main + from comfy.cli_args import args + args.output_directory = args_pytest["output_dir"] + args.listen = args_pytest["listen"] + args.port = args_pytest["port"] + print("running server anyway!") + main() + + # This initializes args at the beginning of the test session -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session", autouse=False) def args_pytest(pytestconfig): args = {} args['output_dir'] = pytestconfig.getoption('output_dir') @@ -19,9 +36,41 @@ def args_pytest(pytestconfig): return args + +@pytest.fixture(scope="module", autouse=False) +def comfy_background_server(args_pytest): + import multiprocessing + import torch + # Start server + + pickled_args = { + "output_dir": args_pytest["output_dir"], + "listen": args_pytest["listen"], + "port": args_pytest["port"] + } + p = multiprocessing.Process(target=run_server, args=(pickled_args,)) + p.start() + # wait for http url to be ready + success = False + for i in range(60): + try: + with urllib.request.urlopen(f"http://localhost:{pickled_args['port']}/object_info") as response: + success = response.status == 200 + if success: + break + except: + pass + time.sleep(1) + if not success: + raise Exception("Failed to start background server") + yield + p.kill() + torch.cuda.empty_cache() + + def pytest_collection_modifyitems(items): # Modifies items so tests run in the correct order - + LAST_TESTS = ['test_quality'] # Move the last items to the end @@ -29,7 +78,7 @@ def pytest_collection_modifyitems(items): for test_name in LAST_TESTS: for item in items.copy(): print(item.module.__name__, item) - if item.module.__name__ == test_name: + if item.module.__name__ == test_name: last_items.append(item) items.remove(item) diff --git a/tests/distributed/__init__.py b/tests/distributed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/distributed/test_asyncio_remote_client.py b/tests/distributed/test_asyncio_remote_client.py new file mode 100644 index 000000000..0ea3dbe66 --- /dev/null +++ b/tests/distributed/test_asyncio_remote_client.py @@ -0,0 +1,19 @@ +import pytest +from comfy.client.aio_client import AsyncRemoteComfyClient +from comfy.client.sdxl_with_refiner_workflow import sdxl_workflow_with_refiner + + +@pytest.mark.asyncio +async def test_completes_prompt(comfy_background_server): + client = AsyncRemoteComfyClient() + prompt = sdxl_workflow_with_refiner("test", inference_steps=1, refiner_steps=1) + png_image_bytes = await client.queue_prompt(prompt) + assert len(png_image_bytes) > 1000 + +@pytest.mark.asyncio +async def test_completes_prompt_with_ui(comfy_background_server): + client = AsyncRemoteComfyClient() + prompt = sdxl_workflow_with_refiner("test", inference_steps=1, refiner_steps=1) + result_dict = await client.queue_prompt_ui(prompt) + # should contain one output + assert len(result_dict) == 1 diff --git a/tests/distributed/test_distributed_queue.py b/tests/distributed/test_distributed_queue.py new file mode 100644 index 000000000..496af74a7 --- /dev/null +++ b/tests/distributed/test_distributed_queue.py @@ -0,0 +1,39 @@ +import os +import uuid + +import pytest + +from comfy.client.embedded_comfy_client import EmbeddedComfyClient, ServerStub +from comfy.client.sdxl_with_refiner_workflow import sdxl_workflow_with_refiner +from comfy.component_model.make_mutable import make_mutable +from comfy.component_model.queue_types import QueueItem, QueueTuple, TaskInvocation +from comfy.distributed.distributed_prompt_worker import DistributedPromptWorker + + +@pytest.mark.asyncio +async def test_basic_queue_worker() -> None: + os.environ["TC_HOST"] = "localhost" + # there are lots of side effects from importing that we have to deal with + from testcontainers.rabbitmq import RabbitMqContainer + + with RabbitMqContainer("rabbitmq:latest") as rabbitmq: + params = rabbitmq.get_connection_params() + async with EmbeddedComfyClient() as client: + async with DistributedPromptWorker(client, + connection_uri=f"amqp://guest:guest@127.0.0.1:{params.port}") as worker: + # this unfortunately does a bunch of initialization on the test thread + from comfy.cmd.execution import validate_prompt + from comfy.distributed.distributed_prompt_queue import DistributedPromptQueue + # now submit some jobs + distributed_queue = DistributedPromptQueue(ServerStub(), is_callee=False, is_caller=True, + connection_uri=f"amqp://guest:guest@127.0.0.1:{params.port}") + await distributed_queue.init() + prompt = make_mutable(sdxl_workflow_with_refiner("test", inference_steps=1, refiner_steps=1)) + validation_tuple = validate_prompt(prompt) + item_id = str(uuid.uuid4()) + queue_tuple: QueueTuple = (0, item_id, prompt, {}, validation_tuple[2]) + res: TaskInvocation = await distributed_queue.put_async(QueueItem(queue_tuple, None)) + assert res.item_id == item_id + assert len(res.outputs) == 1 + assert res.status is not None + assert res.status.status_str == "success" diff --git a/tests/distributed/test_embedded_client.py b/tests/distributed/test_embedded_client.py new file mode 100644 index 000000000..b48d9b869 --- /dev/null +++ b/tests/distributed/test_embedded_client.py @@ -0,0 +1,33 @@ +import pytest +import torch + +from comfy.client.embedded_comfy_client import EmbeddedComfyClient +from comfy.client.sdxl_with_refiner_workflow import sdxl_workflow_with_refiner + + +@pytest.mark.asyncio +async def test_cuda_memory_usage(): + if not torch.cuda.is_available(): + pytest.skip("CUDA is not available in this environment") + + device = torch.device("cuda") + starting_memory = torch.cuda.memory_allocated(device) + + async with EmbeddedComfyClient() as client: + prompt = sdxl_workflow_with_refiner("test") + outputs = await client.queue_prompt(prompt) + assert outputs["13"]["images"][0]["abs_path"] is not None + memory_after_workflow = torch.cuda.memory_allocated(device) + assert memory_after_workflow > starting_memory, "Expected CUDA memory to increase after running the workflow" + + ending_memory = torch.cuda.memory_allocated(device) + assert abs( + ending_memory - starting_memory) < 1e7, "Expected CUDA memory to return close to starting memory after cleanup" + + +@pytest.mark.asyncio +async def test_embedded_comfy(): + async with EmbeddedComfyClient() as client: + prompt = sdxl_workflow_with_refiner("test") + outputs = await client.queue_prompt(prompt) + assert outputs["13"]["images"][0]["abs_path"] is not None diff --git a/tests/inference/graphs/default_graph_sdxl1_0.json b/tests/inference/graphs/default_graph_sdxl1_0.json index c06c6829c..acba1022d 100644 --- a/tests/inference/graphs/default_graph_sdxl1_0.json +++ b/tests/inference/graphs/default_graph_sdxl1_0.json @@ -1,144 +1,144 @@ { - "4": { - "inputs": { - "ckpt_name": "sd_xl_base_1.0.safetensors" - }, - "class_type": "CheckpointLoaderSimple" + "4": { + "inputs": { + "ckpt_name": "sd_xl_base_1.0.safetensors" }, - "5": { - "inputs": { - "width": 1024, - "height": 1024, - "batch_size": 1 - }, - "class_type": "EmptyLatentImage" + "class_type": "CheckpointLoaderSimple" + }, + "5": { + "inputs": { + "width": 1024, + "height": 1024, + "batch_size": 1 }, - "6": { - "inputs": { - "text": "a photo of a cat", - "clip": [ - "4", - 1 - ] - }, - "class_type": "CLIPTextEncode" + "class_type": "EmptyLatentImage" + }, + "6": { + "inputs": { + "text": "a photo of a cat", + "clip": [ + "4", + 1 + ] }, - "10": { - "inputs": { - "add_noise": "enable", - "noise_seed": 42, - "steps": 20, - "cfg": 7.5, - "sampler_name": "euler", - "scheduler": "normal", - "start_at_step": 0, - "end_at_step": 32, - "return_with_leftover_noise": "enable", - "model": [ - "4", - 0 - ], - "positive": [ - "6", - 0 - ], - "negative": [ - "15", - 0 - ], - "latent_image": [ - "5", - 0 - ] - }, - "class_type": "KSamplerAdvanced" + "class_type": "CLIPTextEncode" + }, + "10": { + "inputs": { + "add_noise": "enable", + "noise_seed": 42, + "steps": 20, + "cfg": 7.5, + "sampler_name": "euler", + "scheduler": "normal", + "start_at_step": 0, + "end_at_step": 32, + "return_with_leftover_noise": "enable", + "model": [ + "4", + 0 + ], + "positive": [ + "6", + 0 + ], + "negative": [ + "15", + 0 + ], + "latent_image": [ + "5", + 0 + ] }, - "12": { - "inputs": { - "samples": [ - "14", - 0 - ], - "vae": [ - "4", - 2 - ] - }, - "class_type": "VAEDecode" + "class_type": "KSamplerAdvanced" + }, + "12": { + "inputs": { + "samples": [ + "14", + 0 + ], + "vae": [ + "4", + 2 + ] }, - "13": { - "inputs": { - "filename_prefix": "test_inference", - "images": [ - "12", - 0 - ] - }, - "class_type": "SaveImage" + "class_type": "VAEDecode" + }, + "13": { + "inputs": { + "filename_prefix": "test_inference", + "images": [ + "12", + 0 + ] }, - "14": { - "inputs": { - "add_noise": "disable", - "noise_seed": 42, - "steps": 20, - "cfg": 7.5, - "sampler_name": "euler", - "scheduler": "normal", - "start_at_step": 32, - "end_at_step": 10000, - "return_with_leftover_noise": "disable", - "model": [ - "16", - 0 - ], - "positive": [ - "17", - 0 - ], - "negative": [ - "20", - 0 - ], - "latent_image": [ - "10", - 0 - ] - }, - "class_type": "KSamplerAdvanced" + "class_type": "SaveImage" + }, + "14": { + "inputs": { + "add_noise": "disable", + "noise_seed": 42, + "steps": 20, + "cfg": 7.5, + "sampler_name": "euler", + "scheduler": "normal", + "start_at_step": 32, + "end_at_step": 10000, + "return_with_leftover_noise": "disable", + "model": [ + "16", + 0 + ], + "positive": [ + "17", + 0 + ], + "negative": [ + "20", + 0 + ], + "latent_image": [ + "10", + 0 + ] }, - "15": { - "inputs": { - "conditioning": [ - "6", - 0 - ] - }, - "class_type": "ConditioningZeroOut" + "class_type": "KSamplerAdvanced" + }, + "15": { + "inputs": { + "conditioning": [ + "6", + 0 + ] }, - "16": { - "inputs": { - "ckpt_name": "sd_xl_refiner_1.0.safetensors" - }, - "class_type": "CheckpointLoaderSimple" + "class_type": "ConditioningZeroOut" + }, + "16": { + "inputs": { + "ckpt_name": "sd_xl_refiner_1.0.safetensors" }, - "17": { - "inputs": { - "text": "a photo of a cat", - "clip": [ - "16", - 1 - ] - }, - "class_type": "CLIPTextEncode" + "class_type": "CheckpointLoaderSimple" + }, + "17": { + "inputs": { + "text": "a photo of a cat", + "clip": [ + "16", + 1 + ] }, - "20": { - "inputs": { - "text": "", - "clip": [ - "16", - 1 - ] - }, - "class_type": "CLIPTextEncode" - } - } \ No newline at end of file + "class_type": "CLIPTextEncode" + }, + "20": { + "inputs": { + "text": "", + "clip": [ + "16", + 1 + ] + }, + "class_type": "CLIPTextEncode" + } +} \ No newline at end of file diff --git a/tests/inference/test_inference.py b/tests/inference/test_inference.py index 76910ad86..94ffe9eaf 100644 --- a/tests/inference/test_inference.py +++ b/tests/inference/test_inference.py @@ -16,7 +16,7 @@ import uuid import urllib.request import urllib.parse -from comfy.samplers import KSampler +from comfy.sampler_names import SAMPLER_NAMES, SCHEDULER_NAMES """ These tests generate and save images through a range of parameters @@ -140,16 +140,9 @@ prompt_list = [ 'a painting of a cat', ] -sampler_list = KSampler.SAMPLERS -scheduler_list = KSampler.SCHEDULERS +sampler_list = SAMPLER_NAMES[:] +scheduler_list = SCHEDULER_NAMES[:] -def run_server(args_pytest): - from comfy.cmd.main import main - from comfy.cli_args import args - args.output_directory = args_pytest["output_dir"] - args.listen = args_pytest["listen"] - args.port = args_pytest["port"] - main() @pytest.mark.inference @pytest.mark.parametrize("sampler", sampler_list) @@ -159,21 +152,7 @@ class TestInference: # # Initialize server and client # - @fixture(scope="class", autouse=True) - def _server(self, args_pytest): - import multiprocessing - # Start server - pickled_args = { - "output_dir": args_pytest["output_dir"], - "listen": args_pytest["listen"], - "port": args_pytest["port"] - } - p = multiprocessing.Process(target=run_server, args=(pickled_args,)) - p.start() - yield - p.kill() - torch.cuda.empty_cache() def start_client(self, listen: str, port: int): # Start client @@ -196,8 +175,8 @@ class TestInference: # # Returns a "_client_graph", which is client-graph pair corresponding to an initialized server # The "graph" is the default graph - @fixture(scope="class", params=comfy_graph_list, ids=comfy_graph_ids, autouse=True) - def _client_graph(self, request, args_pytest, _server) -> (ComfyClient, ComfyGraph): + @fixture(scope="class", params=comfy_graph_list, ids=comfy_graph_ids, autouse=False) + def _client_graph(self, request, args_pytest, comfy_background_server) -> (ComfyClient, ComfyGraph): comfy_graph = request.param # Start client