From 6b6769594fa2946f2dfb3a736fb9a630e6ce63cf Mon Sep 17 00:00:00 2001 From: Ewe Zi Yi <36802364+DeadlyCoconuts@users.noreply.github.com> Date: Tue, 25 Jan 2022 12:00:07 +0800 Subject: [PATCH] Add router management functions to SDK (#155) * Refactor swagger/openapi specs for sdk * Add autogenerated openapi classes for routers * Rewrote OpenAPI spec for ExperimentConfig to avoid using oneOf * Remove dependency of ExperimentConfig schema on ExperimentEngineType * Add additional fields for enricher component * Add additional nullable fields to OpenAPI spec * Reference ensembler, event and router id to common id schema * Rename Ensembler in OpenAPI spec for routers to RouterEnsemblerConfig * Reorganise router related tests * Refactor route tests to utilise predefined fixtures * Add resource_request sdk classes and tests * Add RouterEnsemblerConfig sdk and classes * Move common router components to separate file * Refactor RouterEnsemblerConfig to use common schemas * Add Enricher sdk classes and tests * Add ExperimentEngine sdk class * Add fix to enforce project_id in ExperimentConfig to be read as int * Refactor regex checks for cpu and memory requests * Add create router tests * Refactor string checks and add docstrings * Rewrite router test case * Modify min/max replica comparison check to allow equality * Refactor swagger/openapi specs for sdk * Add autogenerated openapi classes for routers * Rewrote OpenAPI spec for ExperimentConfig to avoid using oneOf * Remove dependency of ExperimentConfig schema on ExperimentEngineType * Add additional fields for enricher component * Add additional nullable fields to OpenAPI spec * Reference ensembler, event and router id to common id schema * Rename Ensembler in OpenAPI spec for routers to RouterEnsemblerConfig * Refactor route tests to utilise predefined fixtures * Add resource_request sdk classes and tests * Add RouterEnsemblerConfig sdk and classes * Move common router components to separate file * Refactor RouterEnsemblerConfig to use common schemas * Add Enricher sdk classes and tests * Add ExperimentEngine sdk class * Add fix to enforce project_id in ExperimentConfig to be read as int * Refactor regex checks for cpu and memory requests * Add create router tests * Refactor string checks and add docstrings * Rewrite router test case * Modify min/max replica comparison check to allow equality * Refactor Enricher, LogConfig, Route, RouterConfig, RouterEnsemblerConfig into dataclasses * Remove regex validation from SDK and move them to OpenAPI spec * Reinsert missing semi-colon in js file * Correct regex strings in OpenApi spec * Rearrange modules in SDK * Add all router API endpoints and reformat response schema for delete router operation * Add delete router method to router sdk class * Add SDK method for getting router by router_id * Add SDK method for updating router config * Add deploy and undeploy router SDK functions * Add tests for create/delete/deploy/undeploy router SDK methods * Add tests for undeploy router SDK method * Add get/delete router version SDK methods * Add tests for get/delete router version SDK methods * Add deploy version/get events router SDK methods * Add list router versions SDK method * Reorganise router module * Add sample file to demonstrate the use of the router class in the SDK * Add docstrings to RouterVersion * Replace print with logging statements and fix comments * Regroup import statements * Add global numbers to implementation of to_dict * Add wait methods for routers to sample; move location of RouterStatus * Replace kwargs with empty dict when created OpenAPI ExperimentConfig * Refactor log_config fixture * Implement logging and tests for blocking functions * Add additional debugging statements for sample * Refactor blocking functions to take floats * Add dataclass implementation to config classes * Remove extra comma in class definition * Add additional sleep duration to ensure router dependencies are undeployed in sample * Add support for experiment plugin configs * Remove plugin_config from SDK and OpenAPI spec * Remove plugin_config from autogenerated classes * Fix update method of Router class to ensure all attributes are updated * Replace images with generic images from Docker Hub * Use generic images and names for tests * Add additional sample to demonstrate creating a new router from an existing one * Remove redundant imports * Wrap sleep statements into router blocking functions Co-authored-by: ewezy --- api/api/openapi-sdk.yaml | 14 + api/api/specs/experiment-engines.yaml | 4 + api/api/specs/routers.yaml | 13 +- .../router/create_from_existing_router.py | 182 +++ sdk/samples/router/general.py | 313 ++++ sdk/tests/conftest.py | 76 +- sdk/tests/router/config/enricher_test.py | 2 +- .../router/config/experiment_config_test.py | 2 - .../config/router_ensembler_config_test.py | 8 +- sdk/tests/router/router_test.py | 398 ++++- .../api_responses/create_router_0000.json | 6 +- sdk/turing/generated/api/router_api.py | 1306 +++++++++++++++++ sdk/turing/generated/model/event.py | 188 +++ .../generated/model/experiment_config.py | 8 +- .../generated/model/inline_response200.py | 166 +++ .../generated/model/inline_response2001.py | 166 +++ .../generated/model/inline_response2002.py | 171 +++ .../generated/model/inline_response202.py | 169 +++ sdk/turing/generated/models/__init__.py | 5 + sdk/turing/router/config/experiment_config.py | 19 +- sdk/turing/router/config/log_config.py | 22 + sdk/turing/router/config/router_config.py | 18 +- .../router/config/router_ensembler_config.py | 2 + sdk/turing/router/config/router_version.py | 55 + sdk/turing/router/config/traffic_rule.py | 2 + sdk/turing/router/router.py | 157 +- sdk/turing/session.py | 117 +- 27 files changed, 3523 insertions(+), 66 deletions(-) create mode 100644 sdk/samples/router/create_from_existing_router.py create mode 100644 sdk/samples/router/general.py create mode 100644 sdk/turing/generated/model/event.py create mode 100644 sdk/turing/generated/model/inline_response200.py create mode 100644 sdk/turing/generated/model/inline_response2001.py create mode 100644 sdk/turing/generated/model/inline_response2002.py create mode 100644 sdk/turing/generated/model/inline_response202.py create mode 100644 sdk/turing/router/config/router_version.py diff --git a/api/api/openapi-sdk.yaml b/api/api/openapi-sdk.yaml index c4c2017dc..c8d4dafb1 100644 --- a/api/api/openapi-sdk.yaml +++ b/api/api/openapi-sdk.yaml @@ -25,3 +25,17 @@ paths: # R O U T E R S "/projects/{project_id}/routers": $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers" + "/projects/{project_id}/routers/{router_id}": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}" + "/projects/{project_id}/routers/{router_id}/deploy": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1deploy" + "/projects/{project_id}/routers/{router_id}/undeploy": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1undeploy" + "/projects/{project_id}/routers/{router_id}/versions": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1versions" + "/projects/{project_id}/routers/{router_id}/versions/{version}": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1versions~1{version}" + "/projects/{project_id}/routers/{router_id}/versions/{version}/deploy": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1versions~1{version}~1deploy" + "/projects/{project_id}/routers/{router_id}/events": + $ref: "specs/routers.yaml#/paths/~1projects~1{project_id}~1routers~1{router_id}~1events" diff --git a/api/api/specs/experiment-engines.yaml b/api/api/specs/experiment-engines.yaml index 12c7bcfc8..0364ba98a 100644 --- a/api/api/specs/experiment-engines.yaml +++ b/api/api/specs/experiment-engines.yaml @@ -296,6 +296,10 @@ components: $ref: "#/components/schemas/ExperimentEngineType" config: type: "object" + additionalProperties: + type: "object" + readOnly: true + nullable: true # Enums ExperimentEngineType: diff --git a/api/api/specs/routers.yaml b/api/api/specs/routers.yaml index 1e3f837d2..f083dd869 100644 --- a/api/api/specs/routers.yaml +++ b/api/api/specs/routers.yaml @@ -154,7 +154,7 @@ paths: schema: type: "object" properties: - router_id: + id: <<: *id 400: description: "Invalid project_id or router_id" @@ -320,7 +320,7 @@ paths: <<: *id required: true responses: - 200: + 202: description: "OK" content: application/json: @@ -401,9 +401,12 @@ paths: content: application/json: schema: - type: "array" - items: - $ref: "#/components/schemas/Event" + type: "object" + properties: + events: + type: "array" + items: + $ref: "#/components/schemas/Event" 400: description: "Invalid project_id or router_id" 404: diff --git a/sdk/samples/router/create_from_existing_router.py b/sdk/samples/router/create_from_existing_router.py new file mode 100644 index 000000000..54aef31b9 --- /dev/null +++ b/sdk/samples/router/create_from_existing_router.py @@ -0,0 +1,182 @@ +import turing +import turing.batch +import turing.batch.config +import turing.router.config.router_config +from turing.router.config.route import Route +from turing.router.config.router_config import RouterConfig +from turing.router.config.router_version import RouterStatus +from turing.router.config.resource_request import ResourceRequest +from turing.router.config.log_config import LogConfig, ResultLoggerType +from turing.router.config.traffic_rule import TrafficRule, HeaderTrafficRuleCondition +from turing.router.config.enricher import Enricher +from turing.router.config.router_ensembler_config import DockerRouterEnsemblerConfig +from turing.router.config.common.env_var import EnvVar +from turing.router.config.experiment_config import ExperimentConfig + + +def main(turing_api: str, project: str): + # Initialize Turing client + turing.set_url(turing_api) + turing.set_project(project) + + # Build a router for the sake of showing how you can retrieve one from the API + # Create some routes + routes = [ + Route( + id='route-1', + endpoint='http://paths.co/route-1', + timeout='20ms' + ), + Route( + id='route-2', + endpoint='http://paths.co/route-2', + timeout='20ms' + ) + ] + + # Create some traffic rules + rules = [ + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='turns', + values=['left'] + ) + ], + routes=[ + 'route-1' + ] + ), + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='turns', + values=['right'] + ) + ], + routes=[ + 'route-2' + ] + ) + ] + + # Create an experiment config ( + experiment_config = ExperimentConfig( + type="xp", + config={ + 'variables': + [ + {'name': 'latitude', 'field': 'farm_lat', 'field_source': 'header'}, + {'name': 'longitude', 'field': 'farm_long', 'field_source': 'header'} + ], + 'project_id': 102 + } + ) + + # Create a resource request config for the router + resource_request = ResourceRequest( + min_replica=0, + max_replica=2, + cpu_request="500m", + memory_request="512Mi" + ) + + # Create a log config for the router + log_config = LogConfig( + result_logger_type=ResultLoggerType.NOP + ) + + # Create an enricher for the router + enricher = Enricher( + image="ealen/echo-server:0.5.1", + resource_request=ResourceRequest( + min_replica=0, + max_replica=2, + cpu_request="500m", + memory_request="512Mi" + ), + endpoint="/", + timeout="60ms", + port=3000, + env=[ + EnvVar( + name="NODES", + value="2" + ) + ] + ) + + # Create an ensembler for the router + ensembler = DockerRouterEnsemblerConfig( + id=1, + image="ealen/echo-server:0.5.1", + resource_request=ResourceRequest( + min_replica=1, + max_replica=3, + cpu_request="500m", + memory_request="512Mi" + ), + endpoint="/echo", + timeout="60ms", + port=3000, + env=[], + ) + + # Create the RouterConfig instance + router_config = RouterConfig( + environment_name="id-dev", + name="my-router-1", + routes=routes, + rules=rules, + default_route_id="test", + experiment_engine=experiment_config, + resource_request=resource_request, + timeout="100ms", + log_config=log_config, + enricher=enricher, + ensembler=ensembler + ) + + # Create a router using the RouterConfig object + router = turing.Router.create(router_config) + print(f"You have created a router with id: {router.id}") + + # Wait for the router to get deployed; note that a router that is PENDING will have None as its router_config + try: + router.wait_for_status(RouterStatus.DEPLOYED) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {router.id} to get deployed.") + + # Imagine we only have the router's id, and would like to retrieve it + router_1 = turing.Router.get(router.id) + + # Now we'd like to create a new router that's similar to router_1, but with some configs modified + # Get the router config from router_1 + router_config = router_1.config + + # Make your desired changes to the config + # Note that router_config.enricher.env is a regular Python list; so you can use methods such as append or extend + router_config.enricher.env.append( + EnvVar( + name="WORKERS", + value="2" + ) + ) + + router_config.resource_request.max_replica = 5 + + # NOTE: If you are using this config (extracted from an existing router) to create a NEW router, remember to give it + # a new name (this will end up being registered as the router name and router names MUST be unique) + router_config.name = "my-router-2" + + # Create your new router with the router_config object + router_2 = turing.Router.create(router_config) + + # Check the routers that you now have + for r in turing.Router.list(): + print(r) + + +if __name__ == '__main__': + import fire + fire.Fire(main) diff --git a/sdk/samples/router/general.py b/sdk/samples/router/general.py new file mode 100644 index 000000000..87b3cf7e8 --- /dev/null +++ b/sdk/samples/router/general.py @@ -0,0 +1,313 @@ +import turing +import turing.batch +import turing.batch.config +import turing.router.config.router_config +from turing.router.config.route import Route +from turing.router.config.router_config import RouterConfig +from turing.router.config.router_version import RouterStatus +from turing.router.config.resource_request import ResourceRequest +from turing.router.config.log_config import LogConfig, ResultLoggerType +from turing.router.config.traffic_rule import TrafficRule, HeaderTrafficRuleCondition, PayloadTrafficRuleCondition +from turing.router.config.enricher import Enricher +from turing.router.config.router_ensembler_config import DockerRouterEnsemblerConfig +from turing.router.config.common.env_var import EnvVar +from turing.router.config.experiment_config import ExperimentConfig + + +def main(turing_api: str, project: str): + # Initialize Turing client + turing.set_url(turing_api) + turing.set_project(project) + + # Build a router config in order to create a router + # Create some routes + routes = [ + Route( + id='meow', + endpoint='http://fox-says.meow', + timeout='20ms' + ), + Route( + id='woof', + endpoint='http://fox-says.woof', + timeout='20ms' + ), + Route( + id='baaa', + endpoint='http://fox-says.baa', + timeout='20ms' + ), + Route( + id='oink', + endpoint='http://fox-says.oink', + timeout='20ms' + ), + Route( + id='ring-ding-ding', + endpoint='http://fox-says.ring-ding-ding', + timeout='20ms' + ) + ] + + # Create some traffic rules + rules = [ + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='name', + values=['cat'] + ) + ], + routes=[ + 'meow' + ] + ), + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='name', + values=['dog'] + ) + ], + routes=[ + 'woof' + ] + ), + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='name', + values=['sheep'] + ) + ], + routes=[ + 'baaa' + ] + ), + TrafficRule( + conditions=[ + HeaderTrafficRuleCondition( + field='name', + values=['pig'] + ) + ], + routes=[ + 'oink' + ] + ), + TrafficRule( + conditions=[ + PayloadTrafficRuleCondition( + field='body', + values=['sus'] + ) + ], + routes=[ + 'meow', + 'woof', + 'baaa', + 'oink', + 'ring-ding-ding' + ] + ) + ] + + # Create an experiment config ( + experiment_config = ExperimentConfig( + type="xp", + config={ + 'variables': + [ + {'name': 'farm_id', 'field': 'farm_id', 'field_source': 'header'}, + {'name': 'country_code', 'field': 'country', 'field_source': 'header'}, + {'name': 'latitude', 'field': 'farm_lat', 'field_source': 'header'}, + {'name': 'longitude', 'field': 'farm_long', 'field_source': 'header'} + ], + 'project_id': 102 + } + ) + + # Create a resource request config for the router + resource_request = ResourceRequest( + min_replica=0, + max_replica=2, + cpu_request="500m", + memory_request="512Mi" + ) + + # Create a log config for the router + log_config = LogConfig( + result_logger_type=ResultLoggerType.NOP + ) + + # Create an enricher for the router + enricher = Enricher( + image="ealen/echo-server:0.5.1", + resource_request=ResourceRequest( + min_replica=0, + max_replica=2, + cpu_request="500m", + memory_request="512Mi" + ), + endpoint="/", + timeout="60ms", + port=3000, + env=[ + EnvVar( + name="humans", + value="farmer-joe" + ) + ] + ) + + # Create an ensembler for the router + ensembler = DockerRouterEnsemblerConfig( + id=1, + image="ealen/echo-server:0.5.1", + resource_request=ResourceRequest( + min_replica=1, + max_replica=3, + cpu_request="500m", + memory_request="512Mi" + ), + endpoint="/echo", + timeout="60ms", + port=3000, + env=[], + ) + + # Create the RouterConfig instance + router_config = RouterConfig( + environment_name="id-dev", + name="what-does-the-fox-say", + routes=routes, + rules=rules, + default_route_id="test", + experiment_engine=experiment_config, + resource_request=resource_request, + timeout="100ms", + log_config=log_config, + enricher=enricher, + ensembler=ensembler + ) + + # 1. Create a new router using the RouterConfig object + new_router = turing.Router.create(router_config) + print(f"1. You have created a router with id: {new_router.id}") + + # 2. List all routers + routers = turing.Router.list() + print(f"2. You have just retrieved a list of {len(routers)} routers:") + for r in routers: + if r.name == new_router.name: + my_router = r + print(r) + + # Wait for the router to get deployed + try: + my_router.wait_for_status(RouterStatus.DEPLOYED) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {my_router.id} to get deployed.") + + # 3. Get the router you just created using the router_id obtained + my_router = turing.Router.get(my_router.id) + print(f"3. You have retrieved the router with name: {my_router.name}") + + # Access the router config from the returned Router object directly + my_router_config = my_router.config + + # Modify something in the router config + my_router_config.routes.append( + Route( + id='fee-fi-fo-fum', + endpoint='http://fox-says.fee-fi-fo-fum', + timeout='20ms' + ) + ) + + # 4. Update the router with the new router config + my_router.update(my_router_config) + print(f"4. You have just updated your router with a new config.") + + # 5. List all the router config versions of your router + my_router_versions = my_router.list_versions() + print(f"5. You have just retrieved a list of {len(my_router_versions)} versions for your router:") + for ver in my_router_versions: + print(ver) + + # Sort the versions returned by version number + my_router_versions.sort(key=lambda x: x.version) + # Get the version number of the first version returned + first_ver_no = my_router_versions[0].version + # Get the version number of the latest version returned + latest_ver_no = my_router_versions[-1].version + + # Wait for the latest version to get deployed + try: + my_router.wait_for_version_status(RouterStatus.DEPLOYED, latest_ver_no) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {my_router.id} with version {latest_ver_no} to get " + f"deployed.") + + # 6. Deploy a specific router config version (the first one we created) + response = my_router.deploy_version(first_ver_no) + print(f"6. You have deployed version {response['version']} of router {response['router_id']}.") + + # Wait for the first version to get deployed + try: + my_router.wait_for_version_status(RouterStatus.DEPLOYED, first_ver_no) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {my_router.id} with version {first_ver_no} to get " + f"deployed.") + + # 7. Undeploy the current active router configuration + response = my_router.undeploy() + print(f"7. You have undeployed router {response['router_id']}.") + + # Wait for the router to get undeployed + try: + my_router.wait_for_status(RouterStatus.UNDEPLOYED) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {my_router.id} to get undeployed.") + + # 8. Deploy the router's *current* configuration (notice how it still deploys the *first* version) + response = my_router.deploy() + print(f"8. You have deployed version {response['version']} of router {response['router_id']}.") + + # Wait for the router to get deployed + try: + my_router.wait_for_status(RouterStatus.DEPLOYED) + except TimeoutError: + raise Exception(f"Turing API is taking too long for router {my_router.id} to get deployed.") + + # Undeploy the router + response = my_router.undeploy() + print(f"You have undeployed router {response['router_id']}.") + + # 9. Get a specific router version of the router + my_router_ver = my_router.get_version(first_ver_no) + print(f"9. You have just retrieved version {my_router_ver.version} of your router.") + + # 10. Delete a specific router version of the router + response = my_router.delete_version(latest_ver_no) + print(f"10. You have deleted version {response['version']} of router {response['router_id']}.") + + # 11. Get all deployment events associated with this router + events = my_router.get_events() + print(f"11. You have just retrieved a list of {len(events)} events for your router:") + for e in events: + print(e) + + # 12. Delete this router (using its router_id) + deleted_router_id = turing.Router.delete(my_router.id) + print(f"12. You have just deleted the router with id: {deleted_router_id}") + + # Check if the router still exists + for r in turing.Router.list(): + if r.name == my_router_config.name: + raise Exception("Oh my, this router still exists!") + + +if __name__ == '__main__': + import fire + fire.Fire(main) diff --git a/sdk/tests/conftest.py b/sdk/tests/conftest.py index 2d96e091f..0efa1e278 100644 --- a/sdk/tests/conftest.py +++ b/sdk/tests/conftest.py @@ -223,18 +223,27 @@ def generic_kafka_config(): ) -@pytest.fixture -def log_config(generic_log_level, generic_result_logger_type, generic_bigquery_config, generic_kafka_config): - return turing.generated.models.RouterVersionLogConfig( +@pytest.fixture(params=["kafka", "bigquery", "others"]) +def log_config(generic_log_level, generic_result_logger_type, generic_bigquery_config, generic_kafka_config, request): + result_logger_type = generic_result_logger_type.value if request.param == "others" else request.param + + params = dict( log_level=generic_log_level, custom_metrics_enabled=True, fiber_debug_log_enabled=True, jaeger_enabled=True, - result_logger_type=generic_result_logger_type, - bigquery_config=generic_bigquery_config, - kafka_config=generic_kafka_config + result_logger_type=turing.generated.models.ResultLoggerType(result_logger_type), + bigquery_config=None, + kafka_config=None ) + if request.param == "kafka": + params["kafka_config"] = generic_kafka_config + elif request.param == "biggquery": + params["bigquery_config"] = generic_bigquery_config + + return turing.generated.models.RouterVersionLogConfig(**params) + @pytest.fixture def generic_route(): @@ -314,7 +323,7 @@ def generic_env_var(): @pytest.fixture def generic_ensembler_docker_config(generic_resource_request, generic_env_var): return turing.generated.models.EnsemblerDockerConfig( - image="test.io/gods-test/turing-ensembler:0.0.0-build.0", + image="test.io/just-a-test/turing-ensembler:0.0.0-build.0", resource_request=generic_resource_request, endpoint=f"http://localhost:5000/ensembler_endpoint", timeout="500ms", @@ -357,7 +366,7 @@ def generic_docker_router_ensembler_config(generic_ensembler_docker_config): def generic_enricher(generic_resource_request, generic_env_var): return turing.generated.models.Enricher( id=1, - image="test.io/gods-test/turing-enricher:0.0.0-build.0", + image="test.io/just-a-test/turing-enricher:0.0.0-build.0", resource_request=generic_resource_request, endpoint=f"http://localhost:5000/enricher_endpoint", timeout="500ms", @@ -395,6 +404,7 @@ def experiment_config(request): } else: config = None + return turing.generated.models.ExperimentConfig( type=experiment_type, config=config @@ -402,7 +412,7 @@ def experiment_config(request): @pytest.fixture -def router_version( +def generic_router_version( generic_router_version_status, generic_route, generic_traffic_rule, @@ -420,7 +430,7 @@ def router_version( version=1, status=generic_router_version_status, error="NONE", - image="test.io/gods-test/turing-router:0.0.0-build.0", + image="test.io/just-a-test/turing-router:0.0.0-build.0", routes=[generic_route for _ in range(2)], default_route="http://models.internal/default", default_route_id="control", @@ -480,7 +490,7 @@ def generic_router_config(): service_account_secret="not-a-secret" ), enricher=Enricher( - image="asia.test.io/model-dev/echo:1.0.2", + image="test.io/model-dev/echo:1.0.2", resource_request=ResourceRequest( min_replica=0, max_replica=2, @@ -499,7 +509,7 @@ def generic_router_config(): ), ensembler=DockerRouterEnsemblerConfig( id=1, - image="asia.test.io/gods-test/turing-ensembler:0.0.0-build.0", + image="test.io/just-a-test/turing-ensembler:0.0.0-build.0", resource_request=ResourceRequest( min_replica=1, max_replica=3, @@ -515,23 +525,23 @@ def generic_router_config(): @pytest.fixture -def generic_router(generic_router_status, router_version): +def generic_router(project, generic_router_status, generic_router_version): return turing.generated.models.RouterDetails( id=1, - name=f"router-1", - endpoint=f"http://localhost:5000/endpoint_1", - environment_name=f"env_1", - monitoring_url=f"http://localhost:5000/dashboard_1", + name="router-1", + endpoint="http://localhost:5000/endpoint_1", + environment_name="env_1", + monitoring_url="http://localhost:5000/dashboard_1", project_id=project.id, status=generic_router_status, created_at=datetime.now(), updated_at=datetime.now(), - config=router_version + config=generic_router_version ) @pytest.fixture -def generic_routers(project, num_routers, generic_router_status, router_version): +def generic_routers(project, num_routers, generic_router_status, generic_router_version): return [ turing.generated.models.RouterDetails( id=i, @@ -543,5 +553,31 @@ def generic_routers(project, num_routers, generic_router_status, router_version) status=generic_router_status, created_at=datetime.now() + timedelta(seconds=i + 10), updated_at=datetime.now() + timedelta(seconds=i + 10), - config=router_version + config=generic_router_version ) for i in range(1, num_routers + 1)] + + +@pytest.fixture +def generic_events(): + return turing.generated.models.InlineResponse2002( + events=[ + turing.generated.models.Event( + created_at=datetime.now(), + updated_at=datetime.now() + timedelta(seconds=1000), + event_type="info", + id=123, + message='successfully deployed router not-a-router version 5', + stage='deployment success', + version=5 + ), + turing.generated.models.Event( + created_at=datetime.now() + timedelta(seconds=1500), + updated_at=datetime.now() + timedelta(seconds=2500), + event_type='error', + id=124, + message='failed to deploy router not-a-router version 5', + stage='deployment failure', + version=5 + ) + ] + ) diff --git a/sdk/tests/router/config/enricher_test.py b/sdk/tests/router/config/enricher_test.py index 362c34fa2..c018fd38d 100644 --- a/sdk/tests/router/config/enricher_test.py +++ b/sdk/tests/router/config/enricher_test.py @@ -8,7 +8,7 @@ "id,image,resource_request,endpoint,timeout,port,env,service_account,expected", [ pytest.param( 1, - "test.io/gods-test/turing-enricher:0.0.0-build.0", + "test.io/just-a-test/turing-enricher:0.0.0-build.0", ResourceRequest( min_replica=1, max_replica=3, diff --git a/sdk/tests/router/config/experiment_config_test.py b/sdk/tests/router/config/experiment_config_test.py index b131a8184..ca88e3de3 100644 --- a/sdk/tests/router/config/experiment_config_test.py +++ b/sdk/tests/router/config/experiment_config_test.py @@ -2,8 +2,6 @@ import turing.generated.models from turing.router.config.experiment_config import ExperimentConfig -from turing.router.config.common.env_var import EnvVar -from turing.router.config.resource_request import ResourceRequest @pytest.mark.parametrize( diff --git a/sdk/tests/router/config/router_ensembler_config_test.py b/sdk/tests/router/config/router_ensembler_config_test.py index 46ddcb6e1..d5de99d4d 100644 --- a/sdk/tests/router/config/router_ensembler_config_test.py +++ b/sdk/tests/router/config/router_ensembler_config_test.py @@ -36,7 +36,7 @@ "docker", None, turing.generated.models.EnsemblerDockerConfig( - image="test.io/gods-test/turing-ensembler:0.0.0-build.0", + image="test.io/just-a-test/turing-ensembler:0.0.0-build.0", resource_request=turing.generated.models.ResourceRequest( min_replica=1, max_replica=3, @@ -70,7 +70,7 @@ def test_create_router_ensembler_config(id, type, standard_config, docker_config "id,image,resource_request,endpoint,timeout,port,env,service_account,expected", [ pytest.param( 1, - "test.io/gods-test/turing-ensembler:0.0.0-build.0", + "test.io/just-a-test/turing-ensembler:0.0.0-build.0", ResourceRequest( min_replica=1, max_replica=3, @@ -165,7 +165,7 @@ def test_create_docker_router_ensembler_config_with_invalid_image( "id,image,resource_request,endpoint,timeout,port,env,service_account,expected", [ pytest.param( 1, - "test.io/gods-test/turing-ensembler:0.0.0-build.0", + "test.io/just-a-test/turing-ensembler:0.0.0-build.0", ResourceRequest( min_replica=1, max_replica=3, @@ -212,7 +212,7 @@ def test_create_docker_router_ensembler_config_with_invalid_timeout( "id,image,resource_request,endpoint,timeout,port,env,service_account,expected", [ pytest.param( 1, - "test.io/gods-test/turing-ensembler:0.0.0-build.0", + "test.io/just-a-test/turing-ensembler:0.0.0-build.0", ResourceRequest( min_replica=1, max_replica=3, diff --git a/sdk/tests/router/router_test.py b/sdk/tests/router/router_test.py index 3fcfd702d..bed20f343 100644 --- a/sdk/tests/router/router_test.py +++ b/sdk/tests/router/router_test.py @@ -5,6 +5,8 @@ import pytest import turing.generated.models from urllib3_mock import Responses +from turing.router.config.router_version import RouterStatus + responses = Responses('requests.packages.urllib3') data_dir = os.path.join(os.path.dirname(__file__), "../testdata/api_responses") @@ -71,7 +73,7 @@ def test_create_router(turing_api, active_project, actual, expected, use_google_ content_type="application/json" ) - actual_response = turing.Router.create(router_config.to_open_api()) + actual_response = turing.Router.create(router_config) actual_config = actual_response.config assert actual_config.environment_name == router_config.environment_name @@ -90,3 +92,397 @@ def test_create_router(turing_api, active_project, actual, expected, use_google_ assert actual_config.enricher.port == router_config.enricher.port assert actual_config.ensembler.type == router_config.ensembler.type + + +@responses.activate +@pytest.mark.parametrize( + "actual,expected", [ + pytest.param( + 1, + turing.generated.models.InlineResponse200(id=1) + ) + ] +) +def test_delete_router(turing_api, active_project, actual, expected, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + responses.add( + method="DELETE", + url=f"/v1/projects/{active_project.id}/routers/{actual}", + body=json.dumps(expected, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + response = turing.Router.delete(1) + assert actual == response + + +@responses.activate +def test_get_router(turing_api, active_project, generic_router, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + actual_id = 1 + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{actual_id}", + body=json.dumps(generic_router, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + response = turing.Router.get(actual_id) + assert actual_id == response.id + + +@responses.activate +@pytest.mark.parametrize( + "actual,expected", [ + pytest.param( + "generic_router_config", + create_router_0000 + ) + ] +) +def test_update_router(turing_api, active_project, actual, expected, use_google_oauth, request): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router( + id=1, + name="router-1", + project_id=active_project.id, + environment_name="id-dev", + monitoring_url="http://localhost:5000/endpoint_1", + status=turing.router.config.router_version.RouterStatus.DEPLOYED, + ) + + router_config = request.getfixturevalue(actual) + + responses.add( + method="PUT", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}", + body=expected, + status=200, + content_type="application/json" + ) + + actual_response = base_router.update(router_config) + actual_config = actual_response.config + + assert actual_config.environment_name == router_config.environment_name + assert actual_config.name == router_config.name + assert actual_config.rules == router_config.rules + assert actual_config.default_route_id == router_config.default_route_id + assert actual_config.experiment_engine.to_open_api() == router_config.experiment_engine.to_open_api() + assert actual_config.resource_request.to_open_api() == router_config.resource_request.to_open_api() + assert actual_config.timeout == router_config.timeout + assert actual_config.log_config.to_open_api() == router_config.log_config.to_open_api() + + assert actual_config.enricher.image == router_config.enricher.image + assert actual_config.enricher.resource_request.to_open_api() == router_config.enricher.resource_request.to_open_api() + assert actual_config.enricher.endpoint == router_config.enricher.endpoint + assert actual_config.enricher.timeout == router_config.enricher.timeout + assert actual_config.enricher.port == router_config.enricher.port + + assert actual_config.ensembler.type == router_config.ensembler.type + + +@responses.activate +def test_deploy_router(turing_api, active_project, generic_router, generic_router_version, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + expected = turing.generated.models.InlineResponse202( + router_id=1, + version=1 + ) + + responses.add( + method="POST", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/deploy", + body=json.dumps(expected, default=tests.json_serializer), + status=202, + content_type="application/json" + ) + + response = base_router.deploy() + assert base_router.id == response['router_id'] + assert generic_router.config.version == response['version'] + + +@responses.activate +def test_undeploy_router(turing_api, active_project, generic_router, generic_router_version, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + expected = turing.generated.models.InlineResponse2001( + router_id=1, + ) + + responses.add( + method="POST", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/undeploy", + body=json.dumps(expected, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + response = base_router.undeploy() + assert base_router.id == response['router_id'] + + +@responses.activate +def test_list_versions(turing_api, active_project, generic_router, generic_router_version, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + expected_versions = [generic_router_version for _ in range(3)] + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions", + body=json.dumps(expected_versions, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + actual_versions = base_router.list_versions() + + assert len(actual_versions) == len(expected_versions) + + for actual, expected in zip(actual_versions, expected_versions): + assert actual.id == generic_router_version.id + assert actual.monitoring_url == generic_router_version.monitoring_url + assert actual.status.value == generic_router_version.status.value + assert actual.created_at == generic_router_version.created_at + assert actual.updated_at == generic_router_version.updated_at + + +@responses.activate +def test_get_version(turing_api, active_project, generic_router, generic_router_version, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + actual_version = 1 + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions/{actual_version}", + body=json.dumps(generic_router_version, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + actual_response = base_router.get_version(actual_version) + + assert actual_response.id == generic_router_version.id + assert actual_response.monitoring_url == generic_router_version.monitoring_url + assert actual_response.status.value == generic_router_version.status.value + assert actual_response.created_at == generic_router_version.created_at + assert actual_response.updated_at == generic_router_version.updated_at + + +@responses.activate +def test_get_version_config(turing_api, active_project, generic_router, generic_router_version, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + actual_version = 1 + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions/{actual_version}", + body=json.dumps(generic_router_version, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + actual_response = base_router.get_version(actual_version).get_config() + + assert actual_response.environment_name == base_router.config.environment_name + assert actual_response.name == base_router.config.name + assert actual_response.rules == base_router.config.rules + assert actual_response.default_route_id == base_router.config.default_route_id + assert actual_response.experiment_engine.to_open_api() == base_router.config.experiment_engine.to_open_api() + assert actual_response.resource_request.to_open_api() == base_router.config.resource_request.to_open_api() + assert actual_response.timeout == base_router.config.timeout + assert actual_response.log_config.to_open_api() == base_router.config.log_config.to_open_api() + + +@responses.activate +def test_delete_version(turing_api, active_project, generic_router, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + expected_router_id = 1 + expected_version = 1 + expected = turing.generated.models.InlineResponse202( + router_id=expected_router_id, + version=expected_version + ) + + responses.add( + method="DELETE", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions/{expected_version}", + body=json.dumps(expected, default=tests.json_serializer), + status=202, + content_type="application/json" + ) + + response = base_router.delete_version(1) + assert base_router.id == response['router_id'] + assert generic_router.config.version == response['version'] + + +@responses.activate +def test_deploy_version(turing_api, active_project, generic_router, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + expected_router_id = 1 + expected_version = 1 + expected = turing.generated.models.InlineResponse202( + router_id=expected_router_id, + version=expected_version + ) + + responses.add( + method="POST", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions/{expected_version}/deploy", + body=json.dumps(expected, default=tests.json_serializer), + status=202, + content_type="application/json" + ) + + response = base_router.deploy_version(1) + assert base_router.id == response['router_id'] + assert generic_router.config.version == response['version'] + + +@responses.activate +def test_get_events_list(turing_api, active_project, generic_router, generic_events, use_google_oauth): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/events", + body=json.dumps(generic_events, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + response = base_router.get_events() + expected_events = generic_events.get('events') + + assert len(response) == len(expected_events) + + for actual, expected in zip(response, expected_events): + assert actual.id == expected.id + assert actual.version == expected.version + assert actual.event_type == expected.event_type + assert actual.stage == expected.stage + assert actual.message == expected.message + assert actual.created_at == expected.created_at + assert actual.updated_at == expected.updated_at + + +@responses.activate +@pytest.mark.parametrize( + "status,max_tries,duration,expected", [ + pytest.param( + RouterStatus.DEPLOYED, + 5, + 0.00001, + TimeoutError + ) + ] +) +def test_wait_for_status( + turing_api, + active_project, + generic_router, + status, + max_tries, + duration, + expected, + use_google_oauth +): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + generic_router.status = turing.generated.models.RouterStatus('pending') + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}", + body=json.dumps(generic_router, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + with pytest.raises(expected): + base_router.wait_for_status(status, max_tries=max_tries, duration=duration) + + +@responses.activate +@pytest.mark.parametrize( + "version,status,max_tries,duration,expected", [ + pytest.param( + 1, + RouterStatus.DEPLOYED, + 5, + 0.00001, + TimeoutError + ) + ] +) +def test_wait_for_version_status( + turing_api, + active_project, + generic_router, + generic_router_version, + version, + status, + max_tries, + duration, + expected, + use_google_oauth +): + turing.set_url(turing_api, use_google_oauth) + turing.set_project(active_project.name) + + base_router = turing.Router.from_open_api(generic_router) + generic_router_version.status = turing.generated.models.RouterVersionStatus('pending') + + responses.add( + method="GET", + url=f"/v1/projects/{active_project.id}/routers/{base_router.id}/versions/{version}", + body=json.dumps(generic_router_version, default=tests.json_serializer), + status=200, + content_type="application/json" + ) + + with pytest.raises(expected): + base_router.wait_for_version_status(status, version=version, max_tries=max_tries, duration=duration) diff --git a/sdk/tests/testdata/api_responses/create_router_0000.json b/sdk/tests/testdata/api_responses/create_router_0000.json index d5a594805..48b08a5fa 100644 --- a/sdk/tests/testdata/api_responses/create_router_0000.json +++ b/sdk/tests/testdata/api_responses/create_router_0000.json @@ -14,7 +14,7 @@ "router": null, "version": 1, "status": "deployed", - "image": "asia.test.io/model-dev/turing-router:1.2.0-build.4-d6c960e", + "image": "test.io/model-dev/turing-router:1.2.0-build.4-d6c960e", "routes": [ { "id": "model-a", @@ -78,7 +78,7 @@ "id": 189, "created_at": "2022-01-12T09:22:22.540551Z", "updated_at": "2022-01-12T09:22:54.325664Z", - "image": "asia.test.io/model-dev/echo:1.0.2", + "image": "test.io/model-dev/echo:1.0.2", "resource_request": { "min_replica": 0, "max_replica": 2, @@ -103,7 +103,7 @@ "type": "docker", "standard_config": null, "docker_config": { - "image": "asia.test.io/gods-test/turing-ensembler:0.0.0-build.0", + "image": "test.io/just-a-test/turing-ensembler:0.0.0-build.0", "resource_request": { "min_replica": 1, "max_replica": 3, diff --git a/sdk/turing/generated/api/router_api.py b/sdk/turing/generated/api/router_api.py index d788105e6..d48a72039 100644 --- a/sdk/turing/generated/api/router_api.py +++ b/sdk/turing/generated/api/router_api.py @@ -21,8 +21,13 @@ none_type, validate_and_convert_types ) +from turing.generated.model.inline_response200 import InlineResponse200 +from turing.generated.model.inline_response2001 import InlineResponse2001 +from turing.generated.model.inline_response2002 import InlineResponse2002 +from turing.generated.model.inline_response202 import InlineResponse202 from turing.generated.model.router_config import RouterConfig from turing.generated.model.router_details import RouterDetails +from turing.generated.model.router_version import RouterVersion class RouterApi(object): @@ -279,3 +284,1304 @@ def __projects_project_id_routers_post( api_client=api_client, callable=__projects_project_id_routers_post ) + + def __projects_project_id_routers_router_id_delete( + self, + project_id, + router_id, + **kwargs + ): + """Delete router # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_delete(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project of the router + router_id (int): id of the router to delete + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse200 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_delete = _Endpoint( + settings={ + 'response_type': (InlineResponse200,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}', + 'operation_id': 'projects_project_id_routers_router_id_delete', + 'http_method': 'DELETE', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_delete + ) + + def __projects_project_id_routers_router_id_deploy_post( + self, + project_id, + router_id, + **kwargs + ): + """Deploy the current router configuration # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_deploy_post(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): id of the project that the router belongs to + router_id (int): id of the router to be deployed + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse202 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_deploy_post = _Endpoint( + settings={ + 'response_type': (InlineResponse202,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/deploy', + 'operation_id': 'projects_project_id_routers_router_id_deploy_post', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_deploy_post + ) + + def __projects_project_id_routers_router_id_events_get( + self, + project_id, + router_id, + **kwargs + ): + """Get deployment events associated with this router # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_events_get(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): id of the project that the router belongs to + router_id (int): id of the router to be deployed + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse2002 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_events_get = _Endpoint( + settings={ + 'response_type': (InlineResponse2002,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/events', + 'operation_id': 'projects_project_id_routers_router_id_events_get', + 'http_method': 'GET', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_events_get + ) + + def __projects_project_id_routers_router_id_get( + self, + project_id, + router_id, + **kwargs + ): + """Get router belonging to project by ID # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_get(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project to retrieve routers from + router_id (int): id of the router to be retrieved + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + RouterDetails + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_get = _Endpoint( + settings={ + 'response_type': (RouterDetails,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}', + 'operation_id': 'projects_project_id_routers_router_id_get', + 'http_method': 'GET', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_get + ) + + def __projects_project_id_routers_router_id_put( + self, + project_id, + router_id, + router_config, + **kwargs + ): + """Update router # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_put(project_id, router_id, router_config, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project of the router + router_id (int): id of the router to update + router_config (RouterConfig): router configuration to save + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + RouterDetails + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + kwargs['router_config'] = \ + router_config + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_put = _Endpoint( + settings={ + 'response_type': (RouterDetails,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}', + 'operation_id': 'projects_project_id_routers_router_id_put', + 'http_method': 'PUT', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + 'router_config', + ], + 'required': [ + 'project_id', + 'router_id', + 'router_config', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + 'router_config': + (RouterConfig,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + 'router_config': 'body', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [ + 'application/json' + ] + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_put + ) + + def __projects_project_id_routers_router_id_undeploy_post( + self, + project_id, + router_id, + **kwargs + ): + """Undeploy router configuration # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_undeploy_post(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): id of the project that the router belongs to + router_id (int): id of the router to undeploy + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse2001 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_undeploy_post = _Endpoint( + settings={ + 'response_type': (InlineResponse2001,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/undeploy', + 'operation_id': 'projects_project_id_routers_router_id_undeploy_post', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_undeploy_post + ) + + def __projects_project_id_routers_router_id_versions_get( + self, + project_id, + router_id, + **kwargs + ): + """List router config versions # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_versions_get(project_id, router_id, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project to retrieve routers from + router_id (int): id of the router to be retrieved + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + [RouterVersion] + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_versions_get = _Endpoint( + settings={ + 'response_type': ([RouterVersion],), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/versions', + 'operation_id': 'projects_project_id_routers_router_id_versions_get', + 'http_method': 'GET', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + ], + 'required': [ + 'project_id', + 'router_id', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_versions_get + ) + + def __projects_project_id_routers_router_id_versions_version_delete( + self, + project_id, + router_id, + version, + **kwargs + ): + """Delete router version # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_versions_version_delete(project_id, router_id, version, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project of the router + router_id (int): id of the router + version (int): version of router configuration to delete + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse202 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + kwargs['version'] = \ + version + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_versions_version_delete = _Endpoint( + settings={ + 'response_type': (InlineResponse202,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/versions/{version}', + 'operation_id': 'projects_project_id_routers_router_id_versions_version_delete', + 'http_method': 'DELETE', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + 'version', + ], + 'required': [ + 'project_id', + 'router_id', + 'version', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + 'version': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + 'version': 'version', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + 'version': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_versions_version_delete + ) + + def __projects_project_id_routers_router_id_versions_version_deploy_post( + self, + project_id, + router_id, + version, + **kwargs + ): + """Deploy specified version of router configuration # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_versions_version_deploy_post(project_id, router_id, version, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): id of the project that the router belongs to + router_id (int): id of the router to be deployed + version (int): version of router configuration to be deployed + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + InlineResponse202 + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + kwargs['version'] = \ + version + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_versions_version_deploy_post = _Endpoint( + settings={ + 'response_type': (InlineResponse202,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/versions/{version}/deploy', + 'operation_id': 'projects_project_id_routers_router_id_versions_version_deploy_post', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + 'version', + ], + 'required': [ + 'project_id', + 'router_id', + 'version', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + 'version': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + 'version': 'version', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + 'version': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_versions_version_deploy_post + ) + + def __projects_project_id_routers_router_id_versions_version_get( + self, + project_id, + router_id, + version, + **kwargs + ): + """Get specific router config version # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.projects_project_id_routers_router_id_versions_version_get(project_id, router_id, version, async_req=True) + >>> result = thread.get() + + Args: + project_id (int): project id of the project to retrieve routers from + router_id (int): id of the router to be retrieved + version (int): version of router configuration to be retrieved + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): 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. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + RouterVersion + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['project_id'] = \ + project_id + kwargs['router_id'] = \ + router_id + kwargs['version'] = \ + version + return self.call_with_http_info(**kwargs) + + self.projects_project_id_routers_router_id_versions_version_get = _Endpoint( + settings={ + 'response_type': (RouterVersion,), + 'auth': [], + 'endpoint_path': '/projects/{project_id}/routers/{router_id}/versions/{version}', + 'operation_id': 'projects_project_id_routers_router_id_versions_version_get', + 'http_method': 'GET', + 'servers': None, + }, + params_map={ + 'all': [ + 'project_id', + 'router_id', + 'version', + ], + 'required': [ + 'project_id', + 'router_id', + 'version', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'project_id': + (int,), + 'router_id': + (int,), + 'version': + (int,), + }, + 'attribute_map': { + 'project_id': 'project_id', + 'router_id': 'router_id', + 'version': 'version', + }, + 'location_map': { + 'project_id': 'path', + 'router_id': 'path', + 'version': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__projects_project_id_routers_router_id_versions_version_get + ) diff --git a/sdk/turing/generated/model/event.py b/sdk/turing/generated/model/event.py new file mode 100644 index 000000000..3ed5cd5fd --- /dev/null +++ b/sdk/turing/generated/model/event.py @@ -0,0 +1,188 @@ +""" + Turing Minimal Openapi Spec for SDK + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + The version of the OpenAPI document: 0.0.1 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from turing.generated.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) + + +class Event(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + ('event_type',): { + 'INFO': "info", + 'ERROR': "error", + }, + } + + validations = { + } + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'id': (int,), # noqa: E501 + 'created_at': (datetime,), # noqa: E501 + 'updated_at': (datetime,), # noqa: E501 + 'version': (int,), # noqa: E501 + 'event_type': (str,), # noqa: E501 + 'stage': (str,), # noqa: E501 + 'message': (str,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'id': 'id', # noqa: E501 + 'created_at': 'created_at', # noqa: E501 + 'updated_at': 'updated_at', # noqa: E501 + 'version': 'version', # noqa: E501 + 'event_type': 'event_type', # noqa: E501 + 'stage': 'stage', # noqa: E501 + 'message': 'message', # noqa: E501 + } + + _composed_schemas = {} + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """Event - a model defined in OpenAPI + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + id (int): [optional] # noqa: E501 + created_at (datetime): [optional] # noqa: E501 + updated_at (datetime): [optional] # noqa: E501 + version (int): [optional] # noqa: E501 + event_type (str): [optional] # noqa: E501 + stage (str): [optional] # noqa: E501 + message (str): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) diff --git a/sdk/turing/generated/model/experiment_config.py b/sdk/turing/generated/model/experiment_config.py index 9d7d80b34..d9d707f9a 100644 --- a/sdk/turing/generated/model/experiment_config.py +++ b/sdk/turing/generated/model/experiment_config.py @@ -57,7 +57,13 @@ class ExperimentConfig(ModelNormal): validations = { } - additional_properties_type = None + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + return ({str: (bool, date, datetime, dict, float, int, list, str, none_type)}, none_type,) # noqa: E501 _nullable = False diff --git a/sdk/turing/generated/model/inline_response200.py b/sdk/turing/generated/model/inline_response200.py new file mode 100644 index 000000000..15873ee78 --- /dev/null +++ b/sdk/turing/generated/model/inline_response200.py @@ -0,0 +1,166 @@ +""" + Turing Minimal Openapi Spec for SDK + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + The version of the OpenAPI document: 0.0.1 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from turing.generated.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) + + +class InlineResponse200(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'id': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'id': 'id', # noqa: E501 + } + + _composed_schemas = {} + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """InlineResponse200 - a model defined in OpenAPI + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + id (int): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) diff --git a/sdk/turing/generated/model/inline_response2001.py b/sdk/turing/generated/model/inline_response2001.py new file mode 100644 index 000000000..00f4cb177 --- /dev/null +++ b/sdk/turing/generated/model/inline_response2001.py @@ -0,0 +1,166 @@ +""" + Turing Minimal Openapi Spec for SDK + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + The version of the OpenAPI document: 0.0.1 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from turing.generated.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) + + +class InlineResponse2001(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'router_id': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'router_id': 'router_id', # noqa: E501 + } + + _composed_schemas = {} + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """InlineResponse2001 - a model defined in OpenAPI + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + router_id (int): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) diff --git a/sdk/turing/generated/model/inline_response2002.py b/sdk/turing/generated/model/inline_response2002.py new file mode 100644 index 000000000..3f149ebee --- /dev/null +++ b/sdk/turing/generated/model/inline_response2002.py @@ -0,0 +1,171 @@ +""" + Turing Minimal Openapi Spec for SDK + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + The version of the OpenAPI document: 0.0.1 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from turing.generated.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) + +def lazy_import(): + from turing.generated.model.event import Event + globals()['Event'] = Event + + +class InlineResponse2002(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + lazy_import() + return { + 'events': ([Event],), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'events': 'events', # noqa: E501 + } + + _composed_schemas = {} + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """InlineResponse2002 - a model defined in OpenAPI + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + events ([Event]): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) diff --git a/sdk/turing/generated/model/inline_response202.py b/sdk/turing/generated/model/inline_response202.py new file mode 100644 index 000000000..916edfdf7 --- /dev/null +++ b/sdk/turing/generated/model/inline_response202.py @@ -0,0 +1,169 @@ +""" + Turing Minimal Openapi Spec for SDK + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + The version of the OpenAPI document: 0.0.1 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from turing.generated.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) + + +class InlineResponse202(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + additional_properties_type = None + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'router_id': (int,), # noqa: E501 + 'version': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'router_id': 'router_id', # noqa: E501 + 'version': 'version', # noqa: E501 + } + + _composed_schemas = {} + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """InlineResponse202 - a model defined in OpenAPI + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + router_id (int): [optional] # noqa: E501 + version (int): [optional] # noqa: E501 + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) diff --git a/sdk/turing/generated/models/__init__.py b/sdk/turing/generated/models/__init__.py index 7ac79c6eb..33ee4b7e1 100644 --- a/sdk/turing/generated/models/__init__.py +++ b/sdk/turing/generated/models/__init__.py @@ -44,12 +44,17 @@ from turing.generated.model.ensembling_job_spec import EnsemblingJobSpec from turing.generated.model.ensembling_resources import EnsemblingResources from turing.generated.model.env_var import EnvVar +from turing.generated.model.event import Event from turing.generated.model.experiment_config import ExperimentConfig from turing.generated.model.field_source import FieldSource from turing.generated.model.generic_dataset import GenericDataset from turing.generated.model.generic_ensembler import GenericEnsembler from turing.generated.model.generic_sink import GenericSink from turing.generated.model.id_object import IdObject +from turing.generated.model.inline_response200 import InlineResponse200 +from turing.generated.model.inline_response2001 import InlineResponse2001 +from turing.generated.model.inline_response2002 import InlineResponse2002 +from turing.generated.model.inline_response202 import InlineResponse202 from turing.generated.model.kafka_config import KafkaConfig from turing.generated.model.label import Label from turing.generated.model.log_level import LogLevel diff --git a/sdk/turing/router/config/experiment_config.py b/sdk/turing/router/config/experiment_config.py index 5d4dbec93..0fe174cff 100644 --- a/sdk/turing/router/config/experiment_config.py +++ b/sdk/turing/router/config/experiment_config.py @@ -7,11 +7,13 @@ @dataclass class ExperimentConfig: - type: str = "nop", + type: str = "nop" config: Dict = None - _type: str = field(init=False, repr=False) - _config: str = field(init=False, repr=False) + def __init__(self, type: str = "nop", config: Dict = None, **kwargs): + self.type = type + self.config = config + self.__dict__.update(kwargs) @property def type(self) -> str: @@ -22,17 +24,22 @@ def type(self, type: str): self._type = type @property - def config(self): + def config(self) -> Dict: return self._config @config.setter - def config(self, config): + def config(self, config: Dict): if config is not None and 'project_id' in config: config['project_id'] = int(config['project_id']) self._config = config def to_open_api(self) -> OpenApiModel: + if self.config is None: + config = {} + else: + config = self.config + return turing.generated.models.ExperimentConfig( type=self.type, - config=self.config + config=config ) diff --git a/sdk/turing/router/config/log_config.py b/sdk/turing/router/config/log_config.py index e3b0d0ee7..cbdf2e89f 100644 --- a/sdk/turing/router/config/log_config.py +++ b/sdk/turing/router/config/log_config.py @@ -105,6 +105,7 @@ class InvalidResultLoggerTypeAndConfigCombination(Exception): pass +@dataclass class BigQueryLogConfig(LogConfig): """ Class to create a new log config with a BigQuery config @@ -161,6 +162,7 @@ class KafkaConfigSerializationFormat(Enum): PROTOBUF = "protobuf" +@dataclass class KafkaLogConfig(LogConfig): def __init__(self, brokers: str, @@ -210,3 +212,23 @@ def to_open_api(self) -> OpenApiModel: serialization_format=self.serialization_format.value ) return super().to_open_api() + + +@dataclass +class RouterVersionLogConfig(LogConfig): + log_level: turing.generated.models.LogLevel = None + custom_metrics_enabled: bool = None + fiber_debug_log_enabled: bool = None + jaeger_enabled: bool = None + + def __init__(self, + log_level: turing.generated.models.LogLevel = None, + custom_metrics_enabled: bool = None, + fiber_debug_log_enabled: bool = None, + jaeger_enabled: bool = None, + **kwargs): + self.log_level = log_level + self.custom_metrics_enabled = custom_metrics_enabled + self.fiber_debug_log_enabled = fiber_debug_log_enabled + self.jaeger_enabled = jaeger_enabled + super().__init__(**kwargs) diff --git a/sdk/turing/router/config/router_config.py b/sdk/turing/router/config/router_config.py index 4a17997c0..b8fd81661 100644 --- a/sdk/turing/router/config/router_config.py +++ b/sdk/turing/router/config/router_config.py @@ -1,3 +1,4 @@ +import inspect from typing import List, Dict, Union from collections import Counter from dataclasses import dataclass @@ -7,12 +8,16 @@ from turing.router.config.route import Route from turing.router.config.traffic_rule import TrafficRule from turing.router.config.resource_request import ResourceRequest -from turing.router.config.log_config import LogConfig +from turing.router.config.log_config import LogConfig, ResultLoggerType from turing.router.config.enricher import Enricher from turing.router.config.router_ensembler_config import RouterEnsemblerConfig from turing.router.config.experiment_config import ExperimentConfig +NAME_INDEX = 0 +VALUE_INDEX = 1 + + @dataclass class RouterConfig: """ @@ -52,7 +57,9 @@ def __init__(self, experiment_engine: Union[ExperimentConfig, Dict] = None, resource_request: Union[ResourceRequest, Dict[str, Union[str, int]]] = None, timeout: str = None, - log_config: Union[LogConfig, Dict[str, Union[str, bool, int]]] = None, + log_config: Union[LogConfig, Dict[str, Union[str, bool, int]]] = LogConfig( + result_logger_type=ResultLoggerType.NOP + ), enricher: Union[Enricher, Dict] = None, ensembler: Union[RouterEnsemblerConfig, Dict] = None, **kwargs): @@ -230,3 +237,10 @@ def _verify_no_duplicate_routes(self): raise turing.router.config.route.DuplicateRouteException( f"Routes with duplicate ids are specified for this traffic rule. Duplicate id: {most_common_route_id}" ) + + def to_dict(self): + att_dict = {} + for m in inspect.getmembers(self): + if not inspect.ismethod(m[VALUE_INDEX]) and not m[NAME_INDEX].startswith('_'): + att_dict[m[NAME_INDEX]] = m[VALUE_INDEX] + return att_dict diff --git a/sdk/turing/router/config/router_ensembler_config.py b/sdk/turing/router/config/router_ensembler_config.py index 7eb12ff38..35a487ba0 100644 --- a/sdk/turing/router/config/router_ensembler_config.py +++ b/sdk/turing/router/config/router_ensembler_config.py @@ -99,6 +99,7 @@ def to_open_api(self) -> OpenApiModel: ) +@dataclass class DockerRouterEnsemblerConfig(RouterEnsemblerConfig): def __init__(self, id: int, @@ -205,6 +206,7 @@ def to_open_api(self) -> OpenApiModel: return super().to_open_api() +@dataclass class StandardRouterEnsemblerConfig(RouterEnsemblerConfig): def __init__(self, id: int, diff --git a/sdk/turing/router/config/router_version.py b/sdk/turing/router/config/router_version.py new file mode 100644 index 000000000..6eba225dd --- /dev/null +++ b/sdk/turing/router/config/router_version.py @@ -0,0 +1,55 @@ +import dataclasses +from enum import Enum + +from turing.router.config.router_config import RouterConfig +from turing.router.config.log_config import RouterVersionLogConfig +from datetime import datetime + + +class RouterStatus(Enum): + """ + Status of router + """ + DEPLOYED = "deployed" + UNDEPLOYED = "undeployed" + FAILED = "failed" + PENDING = "pending" + + +@dataclasses.dataclass +class RouterVersion(RouterConfig): + """ + Class to used to contain a RouterVersion. Used when returning a response containing a router's version from Turing + API. Not to be instantiated manually. + """ + def __init__(self, + id: int, + version: int, + created_at: datetime, + updated_at: datetime, + status: str, + environment_name: str, + name: str, + monitoring_url: str, + **kwargs): + self.id = id + self.version = version + self.created_at = created_at + self.updated_at = updated_at + self.environment_name = environment_name + self.status = RouterStatus(status) + self.name = name + self.monitoring_url = monitoring_url + self.log_config = RouterVersionLogConfig(**kwargs.get('log_config')) + super().__init__(environment_name=environment_name, name=name, **kwargs) + + def get_config(self) -> RouterConfig: + """ + Generates a RouterConfig instance from the attributes contained in this object; NOTE that the name and + environment_name of this version gets passed to the generated RouterConfig. This means that if you were to use + the generated RouterConfig for another instance, you would have to change its name, and also maybe its + environment_name + + :return: a new RouterConfig instance containing attributes of this router version + """ + return RouterConfig(**self.to_dict()) diff --git a/sdk/turing/router/config/traffic_rule.py b/sdk/turing/router/config/traffic_rule.py index f7b55ac31..20edcdfd2 100644 --- a/sdk/turing/router/config/traffic_rule.py +++ b/sdk/turing/router/config/traffic_rule.py @@ -94,6 +94,7 @@ class InvalidOperatorException(Exception): pass +@dataclass class HeaderTrafficRuleCondition(TrafficRuleCondition): def __init__(self, field: str, @@ -107,6 +108,7 @@ def __init__(self, super().__init__(field_source=FieldSource.HEADER, field=field, operator="in", values=values) +@dataclass class PayloadTrafficRuleCondition(TrafficRuleCondition): def __init__(self, field: str, diff --git a/sdk/turing/router/router.py b/sdk/turing/router/router.py index 50be28718..eabb4bbbe 100644 --- a/sdk/turing/router/router.py +++ b/sdk/turing/router/router.py @@ -1,20 +1,24 @@ -from enum import Enum +import time +import logging from typing import List, Dict import turing.generated.models from turing._base_types import ApiObject, ApiObjectSpec from turing.router.config.router_config import RouterConfig +from turing.router.config.router_version import RouterVersion, RouterStatus -class RouterStatus(Enum): - """ - Status of router - """ - DEPLOYED = "deployed" - UNDEPLOYED = "undeployed" - FAILED = "failed" - PENDING = "pending" +logger = logging.getLogger('router_sdk_logger') +logger.setLevel(level=logging.INFO) + +ch = logging.StreamHandler() +ch.setLevel(logging.DEBUG) + +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) + +logger.addHandler(ch) @ApiObjectSpec(turing.generated.models.Router) @@ -40,8 +44,11 @@ def __init__(self, self._endpoint = endpoint self._monitoring_url = monitoring_url self._status = RouterStatus(status) + if config is not None: self._config = RouterConfig(name=name, environment_name=environment_name, **config) + else: + self._config = None @property def id(self) -> int: @@ -86,11 +93,139 @@ def list(cls) -> List['Router']: return [Router.from_open_api(item) for item in response] @classmethod - def create(cls, config: turing.generated.models.RouterConfig) -> 'Router': + def create(cls, config: RouterConfig) -> 'Router': """ Create router with a given configuration :param config: configuration of router :return: instance of router created """ - return Router.from_open_api(turing.active_session.create_router(router_config=config)) + return Router.from_open_api(turing.active_session.create_router(router_config=config.to_open_api())) + + @classmethod + def delete(cls, router_id: int) -> int: + """ + Delete specific router given its router ID + + :param router_id: router_id of the router to be deleted + :return: router_id of the deleted router + """ + return turing.active_session.delete_router(router_id=router_id).id + + @classmethod + def get(cls, router_id: int) -> 'Router': + """ + Fetch router by its router ID + + :param router_id: router_id of the router to be fetched + :return: router with the corresponding id + """ + return Router.from_open_api( + turing.active_session.get_router(router_id=router_id)) + + def update(self, config: RouterConfig) -> 'Router': + """ + Update the current router with a new set of configs specified in the RouterConfig argument + + :param config: configuration of router + :return: instance of router updated (self) + """ + self._config = config + updated_router = Router.from_open_api( + turing.active_session.update_router(router_id=self.id, router_config=config.to_open_api()) + ) + self.__dict__ = updated_router.__dict__ + return self + + def deploy(self) -> Dict[str, int]: + """ + Deploy this router + + :return: router_id and version of this router + """ + return turing.active_session.deploy_router(router_id=self.id).to_dict() + + def undeploy(self) -> Dict[str, int]: + """ + Undeploy this router + + :return: router_id of this router + """ + return turing.active_session.undeploy_router(router_id=self.id).to_dict() + + def list_versions(self) -> List['RouterVersion']: + """ + List router versions for this router + + :return: list of router versions + """ + response = turing.active_session.list_router_versions(router_id=self.id) + return [RouterVersion(environment_name=self.environment_name, name=self.name, **ver.to_dict()) + for ver in response] + + def get_version(self, version: int) -> 'RouterVersion': + """ + Fetch a version of this router given a version number + + :return: list of router versions + """ + version = turing.active_session.get_router_version(router_id=self.id, version=version) + return RouterVersion(environment_name=self.environment_name, name=self.name, **version.to_dict()) + + def delete_version(self, version: int) -> Dict[str, int]: + """ + Delete a version of this router given a version number + + + :return: router_id and deleted version of this router + """ + return turing.active_session.delete_router_version(router_id=self.id, version=version).to_dict() + + def deploy_version(self, version: int) -> Dict[str, int]: + """ + Deploy specific router version by its router ID and version + + :return: router_id and version of this router + """ + return turing.active_session.deploy_router_version(router_id=self.id, version=version).to_dict() + + def get_events(self) -> List[turing.generated.models.Event]: + """ + Fetch deployment events associated with the router + + :return: list of events involving this router + """ + response = turing.active_session.get_router_events(router_id=self.id).get('events') + return [event for event in response] if response else [] + + def wait_for_status(self, status: RouterStatus, max_tries: int = 15, duration: float = 10.0): + for i in range(1, max_tries + 1): + logger.debug(f"Checking if router {self.id} is {status.value}...") + cur_status = Router.get(self.id).status + if cur_status == status: + # Wait for backend components to fully resolve + time.sleep(5) + logger.debug(f"Router {self.id} is finally {status.value}.") + return + else: + logger.debug(f"Router {self.id} is {cur_status.value}.") + logger.debug(f"Retrying {i}/{max_tries} time(s): waiting for {duration} seconds before retrying...") + time.sleep(duration) + + raise TimeoutError + + def wait_for_version_status(self, status: RouterStatus, version: int, max_tries: int = 15, duration: float = 10.0): + for i in range(1, max_tries + 1): + logger.debug(f"Checking if router {self.id} with version {version} is {status.value}...") + cur_status = self.get_version(version).status + if cur_status == status: + # Wait for backend components to fully resolve + time.sleep(5) + logger.debug(f"Router {self.id} with version {version} is finally {status.value}.") + return + else: + logger.debug(f"Router {self.id} with version {version} is {cur_status.value}.") + logger.debug(f"Retrying {i}/{max_tries} time(s): waiting for {duration} seconds before retrying...") + time.sleep(duration) + + raise TimeoutError diff --git a/sdk/turing/session.py b/sdk/turing/session.py index 43a60c140..a90252b5c 100644 --- a/sdk/turing/session.py +++ b/sdk/turing/session.py @@ -1,19 +1,14 @@ import os import mlflow from typing import List, Optional + +import turing.generated.models from turing.ensembler import EnsemblerType from turing.generated import ApiClient, Configuration from turing.generated.apis import EnsemblerApi, EnsemblingJobApi, ProjectApi, RouterApi -from turing.generated.models import \ - Project, \ - Ensembler, \ - EnsemblingJob, \ - EnsemblerJobStatus, \ - EnsemblersPaginatedResults, \ - EnsemblingJobPaginatedResults, \ - IdObject, \ - RouterDetails, \ - RouterConfig +from turing.generated.models import (Project, Ensembler, EnsemblingJob, EnsemblerJobStatus, EnsemblersPaginatedResults, + EnsemblingJobPaginatedResults, IdObject, Router, RouterDetails, RouterConfig, + RouterVersion) def require_active_project(f): @@ -218,3 +213,105 @@ def create_router(self, router_config: RouterConfig) -> RouterDetails: """ return RouterApi(self._api_client).projects_project_id_routers_post(project_id=self.active_project.id, router_config=router_config) + + @require_active_project + def delete_router(self, router_id: int) -> IdObject: + """ + Delete router given its router ID + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_delete( + project_id=self.active_project.id, + router_id=router_id + ) + + @require_active_project + def get_router(self, router_id: int) -> Router: + """ + Fetch router by its router ID + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_get( + project_id=self.active_project.id, + router_id=router_id + ) + + @require_active_project + def update_router(self, router_id: int, router_config: RouterConfig) -> Router: + """ + Update router in the active project the user has access to, with a router_config passed as a parameter + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_put(project_id=self.active_project.id, + router_id=router_id, + router_config=router_config) + + @require_active_project + def deploy_router(self, router_id: int) -> IdObject: + """ + Deploy router given its router ID + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_deploy_post( + project_id=self.active_project.id, + router_id=router_id + ) + + @require_active_project + def undeploy_router(self, router_id: int) -> IdObject: + """ + Undeploy router given its router ID + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_undeploy_post( + project_id=self.active_project.id, + router_id=router_id + ) + + @require_active_project + def list_router_versions(self, router_id: int) -> List[RouterVersion]: + """ + List all router versions, that the current user has access to, given the router ID specified + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_versions_get( + project_id=self.active_project.id, + router_id=router_id + ) + + @require_active_project + def get_router_version(self, router_id: int, version: int) -> RouterVersion: + """ + Fetch specific router version by its router ID and version + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_versions_version_get( + project_id=self.active_project.id, + router_id=router_id, + version=version + ) + + @require_active_project + def delete_router_version(self, router_id: int, version: int) -> IdObject: + """ + Delete specific router version given its router ID and version + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_versions_version_delete( + project_id=self.active_project.id, + router_id=router_id, + version=version + ) + + @require_active_project + def deploy_router_version(self, router_id: int, version: int) -> IdObject: + """ + Deploy specific router version by its router ID and version + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_versions_version_deploy_post( + project_id=self.active_project.id, + router_id=router_id, + version=version + ) + + @require_active_project + def get_router_events(self, router_id: int) -> turing.generated.models.InlineResponse2002: + """ + Fetch deployment events associated with the router with the given router ID + """ + return RouterApi(self._api_client).projects_project_id_routers_router_id_events_get( + project_id=self.active_project.id, + router_id=router_id + )