Skip to content

Commit be6de85

Browse files
committed
tests/fix: more comprehensive api object detection
1 parent 28b0fff commit be6de85

File tree

3 files changed

+110
-14
lines changed

3 files changed

+110
-14
lines changed

horde_sdk/ai_horde_api/endpoints.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,17 @@ class AI_HORDE_API_ENDPOINT_SUBPATH(GENERIC_API_ENDPOINT_SUBPATH):
9797
v2_documents_sponsors = "/v2/documents/sponsors"
9898
v2_documents_terms = "/v2/documents/terms"
9999

100-
# v2_styles_image_by_name = "/v2/styles/image_by_name/{style_name}"
101-
# v2_styles_image_by_id = "/v2/styles/image/{style_id}"
102-
# v2_collections_by_name = "/v2/collection_by_name/{collection_name}"
103-
# v2_styles_image_example_by_id = "/v2/styles/image/{style_id}/example"
104-
# v2_styles_text_by_id = "/v2/styles/text/{style_id}"
105-
# v2_styles_image = "/v2/styles/image"
106-
# v2_collections_by_id = "/v2/collections/{collection_id}"
107-
# v2_styles_image_example_by_id_example = "/v2/styles/image/{style_id}/example/{example_id}"
108-
# v2_styles_text = "/v2/styles/text"
109-
# v2_styles_text_by_name = "/v2/styles/text_by_name/{style_name}"
110-
# v2_collections = "/v2/collections"
100+
v2_styles_image_by_name = "/v2/styles/image_by_name/{style_name}"
101+
v2_styles_image_by_id = "/v2/styles/image/{style_id}"
102+
v2_collections_by_name = "/v2/collection_by_name/{collection_name}"
103+
v2_styles_image_example_by_id = "/v2/styles/image/{style_id}/example"
104+
v2_styles_text_by_id = "/v2/styles/text/{style_id}"
105+
v2_styles_image = "/v2/styles/image"
106+
v2_collections_by_id = "/v2/collections/{collection_id}"
107+
v2_styles_image_example_by_id_example = "/v2/styles/image/{style_id}/example/{example_id}"
108+
v2_styles_text = "/v2/styles/text"
109+
v2_styles_text_by_name = "/v2/styles/text_by_name/{style_name}"
110+
v2_collections = "/v2/collections"
111111

112112

113113
def get_ai_horde_swagger_url() -> str:
@@ -116,3 +116,25 @@ def get_ai_horde_swagger_url() -> str:
116116
base_url=AI_HORDE_BASE_URL,
117117
path=AI_HORDE_API_ENDPOINT_SUBPATH.swagger,
118118
)
119+
120+
121+
def get_admin_only_endpoints() -> set[str]:
122+
"""Return all of the endpoints that are admin-only."""
123+
return {
124+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_modes,
125+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_filters,
126+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_filters_regex,
127+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_filters_regex_single,
128+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_operations_block_worker_ipaddr_single,
129+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_operations_ipaddr,
130+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_operations_ipaddr_single,
131+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_kudos_award,
132+
}
133+
134+
135+
def get_deprecated_endpoints() -> set[str]:
136+
"""Return all of the endpoints that are deprecated."""
137+
return {
138+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_pop_multi,
139+
AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_rate_id,
140+
}

horde_sdk/meta.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,35 @@ def any_unimported_classes(module: types.ModuleType, super_type: type) -> tuple[
7474
return bool(missing_classes), missing_classes
7575

7676

77-
def all_undefined_classes(module: types.ModuleType) -> dict[str, str]:
77+
def all_undefined_classes(module: types.ModuleType) -> list[str]:
78+
"""Return all of the models defined on the API but not in the SDK."""
79+
module_found_classes = find_subclasses(module, HordeAPIObject)
80+
81+
defined_api_object_names: set[str] = set()
82+
83+
for class_type in module_found_classes:
84+
if not issubclass(class_type, HordeAPIObject):
85+
raise TypeError(f"Expected {class_type} to be a HordeAPIObject")
86+
87+
api_model_name = class_type.get_api_model_name()
88+
if api_model_name is not None:
89+
defined_api_object_names.add(api_model_name)
90+
91+
undefined_classes: list[str] = []
92+
93+
parser = SwaggerParser(swagger_doc_url=get_ai_horde_swagger_url())
94+
swagger_doc = parser.get_swagger_doc()
95+
96+
all_api_objects = set(swagger_doc.definitions.keys())
97+
missing_object_names = all_api_objects - defined_api_object_names
98+
99+
for object_name in missing_object_names:
100+
undefined_classes.append(object_name)
101+
102+
return undefined_classes
103+
104+
105+
def all_undefined_classes_for_endpoints(module: types.ModuleType) -> dict[str, str]:
78106
"""Return all of the models defined on the API but not in the SDK."""
79107
module_found_classes = find_subclasses(module, HordeAPIObject)
80108

@@ -132,7 +160,6 @@ def all_unaddressed_endpoints_ai_horde() -> set[AI_HORDE_API_ENDPOINT_SUBPATH]:
132160

133161
for path in known_paths:
134162
if path not in all_classes_paths:
135-
print(f"Unaddressed path: {path}")
136163
unaddressed_paths.add(path)
137164

138165
return unaddressed_paths

tests/test_verify_api_surface.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,48 @@ def test_all_ai_horde_api_data_objects_imported() -> None:
4343
@pytest.mark.object_verify
4444
def test_all_ai_horde_api_models_defined() -> None:
4545
import horde_sdk.ai_horde_api.apimodels
46-
from horde_sdk.meta import all_undefined_classes
46+
from horde_sdk.meta import all_undefined_classes, all_undefined_classes_for_endpoints
4747

4848
undefined_classes = all_undefined_classes(horde_sdk.ai_horde_api.apimodels)
4949

50+
# all_undefined_classes_for_endpoints handles the ones directly referenced by endpoints, so we remove them
51+
undefined_classes_for_endpoints = all_undefined_classes_for_endpoints(horde_sdk.ai_horde_api.apimodels)
52+
for key in undefined_classes_for_endpoints:
53+
if key in undefined_classes:
54+
undefined_classes.remove(key)
55+
56+
assert (
57+
"GenerationInputStable" not in undefined_classes
58+
), "A model which is known to be defined in the SDK was not found. Something critically bad has happened."
59+
60+
# Pretty print the undefined classes sorted by dict values, NOT by keys
61+
import json
62+
63+
error_responses = {
64+
"RequestError",
65+
"RequestValidationError",
66+
}
67+
68+
for error_response in error_responses:
69+
if error_response in undefined_classes:
70+
print(f"Warning: {error_response} is an error response which may not be handled.")
71+
undefined_classes.remove(error_response)
72+
73+
undefined_classes_sorted = sorted(undefined_classes)
74+
print(json.dumps(undefined_classes_sorted, indent=4))
75+
76+
assert not undefined_classes, (
77+
"The following models are defined in the API but not in the SDK: " f"{undefined_classes}"
78+
)
79+
80+
81+
@pytest.mark.object_verify
82+
def test_all_ai_horde_api_models_defined_for_endpoints() -> None:
83+
import horde_sdk.ai_horde_api.apimodels
84+
from horde_sdk.meta import all_undefined_classes_for_endpoints
85+
86+
undefined_classes = all_undefined_classes_for_endpoints(horde_sdk.ai_horde_api.apimodels)
87+
5088
assert (
5189
"GenerationInputStable" not in undefined_classes
5290
), "A model which is known to be defined in the SDK was not found. Something critically bad has happened."
@@ -86,10 +124,19 @@ def test_all_ai_horde_endpoints_known() -> None:
86124
# @pytest.mark.skip(reason="This test is not yet enforced.")
87125
@pytest.mark.object_verify
88126
def test_all_ai_horde_endpoints_addressed() -> None:
127+
from horde_sdk.ai_horde_api.endpoints import get_admin_only_endpoints, get_deprecated_endpoints
89128
from horde_sdk.meta import all_unaddressed_endpoints_ai_horde
90129

91130
unaddressed_endpoints = all_unaddressed_endpoints_ai_horde()
92131

132+
all_ignored_endpoints = get_admin_only_endpoints() | get_deprecated_endpoints()
133+
134+
unaddressed_endpoints -= all_ignored_endpoints
135+
136+
print()
137+
for unaddressed_endpoint in unaddressed_endpoints:
138+
print(f"Unaddressed path: {unaddressed_endpoint}.")
139+
93140
assert not unaddressed_endpoints, (
94141
"The following endpoints are defined in the API but not in the SDK: " f"{unaddressed_endpoints}"
95142
)

0 commit comments

Comments
 (0)