From 556574a5adf8ce9a817483eb143627d52778e358 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 20 Jan 2022 18:12:14 -0500 Subject: [PATCH 1/8] add customizations doc --- docs/customizations.md | 292 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 docs/customizations.md diff --git a/docs/customizations.md b/docs/customizations.md new file mode 100644 index 00000000000..96ea376489d --- /dev/null +++ b/docs/customizations.md @@ -0,0 +1,292 @@ +# Grow Up Story for Generated SDKs + +This quickstart will introduce how to group up your generated code with customizations. + +**Note: This quickstart does not focus on renaming or removing generated objects**. If you'd like to rename or remove generated objects, the changes should really be made in the +OpenAPI definition of your service. If you absolutely can not modify the OpenAPI definition, you can use directives, specifically [these pre-defined directives](https://github.com/Azure/autorest/blob/main/docs/generate/built-in-directives.md) in your README configuration + +## Key Concept: \_patch.py + +The `_patch.py` files at each level of the modules will be the entry point to customize the generated code. + +For example, if you want to override a model, you will use the `_patch.py` file at the `models` level of your +generated code to override. + +The main flow of the `_patch.py` file will be: + +1. Import the generated object you wish to override. Generated objects will have the suffix `Generated` +2. Inherit from the generated object, and override its behavior with your desired code functionality +3. Include the name of your customized object in the `__all__` of the `_patch.py` file + +To test that your behavior has been properly customized, please add tests for your customized code. +If you find that your customizations for an object are not being called, please make sure that +your customized object is included in the `__all__` of your `_patch.py` file. + +The `_patch.py` file will never be removed during regeneration, so no worries about your customizations being +lost! + +## Examples + +- [Improve Documentation](#improve-documentation) +- [Change Model Behavior](#change-model-behavior) +- [Add a model](#add-a-model) +- [Change Operation Behavior](#change-operation-behavior) +- [Overload an Operation](#overload-an-operation) +- [Change Client Behavior](#change-client-behavior) +- [Add a Client Method](#add-a-client-method) + +### Improve Documentation + +Documentation for generated objects can be easily improved in the `_patch.py` by overriding generated code. + +In this example, we improve the docstrings for model `Foo` by adding more information. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + └───models + │ _models.py # where the generated models are + | _patch.py # where we customize the models code +``` + +```python +from ._models import FooGenerated + +class Foo(FooGenerated): + """Foo. + + :param str input: Input. This is my new nicer description + """ + +__all__ = ["Foo"] +``` + +### Change Model Behavior + +To override model behavior, you will work with the `_patch.py` file in the `models` folder of your generated code. + +In the following example, we override the generated `Model`'s `input` parameter to accept both `str` and `datetime`, +instead of just `str`. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + └───models + │ _models.py # where the generated models are + | _patch.py # where we customize the models code +``` + +```python +import datetime +from typing import Union +from ._models import ModelGenerated + +class Model(ModelGenerated): + + def __init__(self, input: Union[str, datetime.datetime]): + super().__init__( + input=input.strftime("%d-%b-%Y") if isinstance(input, datetime.datetime) else input + ) + +__all__ = ["Model"] +``` + +### Add a Model + +You can add a model easily in the `_patch.py` file in the models module. Just be sure to include it in that file's `__all__`! + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + └───models + │ _models.py # where the generated models are + | _patch.py # where we customize the models code +``` + +```python +class AddedModel: + + def __init__(self, input: str): + self.input = input + +__all__ = ["AddedModel"] +``` + +### Change Operation Behavior + +To change an operation, you will import the generated operation group the operation is on. Then you can inherit +from the generated operation group and modify the behavior of the operation. + +In the following example, the generated operation takes in a datetime input, and returns a datetime response. +We want to also allow users to input strings, and return a string response if users inputted a string. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + └───operations + │ _operations.py # where the generated operations are + | _patch.py # where we customize the models code +``` + +```python +from typing import Union +import datetime +from ._operations import OperationGroupGenerated + +class OperationGroup(OperationGroupGenerated): + + def operation(self, input: Union[str, datetime.datetime]): + response: datetime.datetime = super().operation( + datetime.datetime.strptime(input, '%b %d %Y') if isinstance(input, str) else input + ) + return response.strftime("%d-%b-%Y") if isinstance(input, str) else response + + +__all__ = ["OperationGroup"] +``` + +### Overload an Operation + +You can also easily overload generated operations. For example, if you want users to be able to pass in the body parameter +as a positional-only single dictionary, or as splatted keyword arguments, you can inherit and override the operation on the operation group +in the `_patch.py` file in the `operations` modules. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + └───operations + │ _operations.py # where the generated operations are + | _patch.py # where we customize the models code +``` + +```python +from typing import overload, Dict, Any +from ._operations import OperationGroupGenerated + +class OperationGroup(OperationGroupGenerated): + + @overload + def operation(self, body: Dict[str, Any], /, **kwargs: Any): + """Pass in the body as a positional only parameter.""" + + @overload + def operation(self, *, foo: str, bar: str, **kwargs: Any): + """Pass in the body as splatted keyword only arguments.""" + + def operation(self, *args, **kwargs): + """Base operation for the two overloads""" + if not args: + args.append({"foo": kwargs.pop("foo"), "bar": kwargs.pop("bar")}) + return super().operation(*args, **kwargs) + +__all__ = ["OperationGroup"] +``` + +### Change Client Behavior + +In this example, we change the default authentication policy for a client. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + │ _service_client.py # where the generated service client is + | _patch.py # where we customize the models code + └───operations + └───models +``` + +```python +from ._service_client import ServiceClientGenerated +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import SansIOHTTPPolicy + +class MyAuthenticationPolicy(SansIOHTTPPolicy): + + def __init__(self, key: str): + self.key = key + + def on_request(self, request: PipelineRequest): + request.http_request.headers["Authorization"] = f"My key is {self.key}" + return super().on_request(request) + +class ServiceClient(ServiceClientGenerated): + + def __init__(self, endpoint: str, credential: str, **kwargs): + super().__init__( + endpoint=endpoint, + credential=credential, + authentication_policy=kwargs.pop("authentication_policy", MyAuthenticationPolicy(credential)), + **kwargs + ) + +__all__ = ["ServiceClient"] +``` + +### Add a Client Method + +Similar to models and operations, you can override client behavior in a `_patch.py` file, this time +at the root of the sdk. + +Here, we will be adding an alternate form of authentication on the client, class method `from_connection_string`. + +In this `_patch.py` file: + +``` +azure-sdk +│ README.md +│ +└───azure + └───sdk + │ _service_client.py # where the generated service client is + | _patch.py # where we customize the models code + └───operations + └───models +``` + +```python +from typing import Any +from azure.core.credentials import AzureKeyCredential +from ._service_client import ServiceClientGenerated + +class ServiceClient(ServiceClientGenerated): + + @classmethod + def from_connection_string(cls, connection_string: str, **kwargs: Any): + parsed_connection_string = _parse_connection_string(connection_string) # parsing function you've defined + return cls( + credential=AzureKeyCredential(parsed_connection_string.pop("accesskey")), + endpoint=parsed_connection_string.pop("endpoint") + ) + +__all__ = ["ServiceClient"] +``` From c33a8fa22bd3df4181ec7d78ebd85febac17e94c Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 21 Jan 2022 12:28:03 -0500 Subject: [PATCH 2/8] fix typo --- docs/customizations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index 96ea376489d..3d1ccd31cff 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -1,11 +1,11 @@ # Grow Up Story for Generated SDKs -This quickstart will introduce how to group up your generated code with customizations. +This quickstart will introduce how to grow up your generated code with customizations. **Note: This quickstart does not focus on renaming or removing generated objects**. If you'd like to rename or remove generated objects, the changes should really be made in the OpenAPI definition of your service. If you absolutely can not modify the OpenAPI definition, you can use directives, specifically [these pre-defined directives](https://github.com/Azure/autorest/blob/main/docs/generate/built-in-directives.md) in your README configuration -## Key Concept: \_patch.py +## Key Concept: _patch.py The `_patch.py` files at each level of the modules will be the entry point to customize the generated code. From ddd362857af652d9e9f2bddb2c1c692498e11419 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Mon, 24 Jan 2022 11:32:36 -0500 Subject: [PATCH 3/8] Update customizations.md --- docs/customizations.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index 3d1ccd31cff..82097e9b69d 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -27,7 +27,6 @@ lost! ## Examples -- [Improve Documentation](#improve-documentation) - [Change Model Behavior](#change-model-behavior) - [Add a model](#add-a-model) - [Change Operation Behavior](#change-operation-behavior) @@ -35,37 +34,6 @@ lost! - [Change Client Behavior](#change-client-behavior) - [Add a Client Method](#add-a-client-method) -### Improve Documentation - -Documentation for generated objects can be easily improved in the `_patch.py` by overriding generated code. - -In this example, we improve the docstrings for model `Foo` by adding more information. - -In this `_patch.py` file: - -``` -azure-sdk -│ README.md -│ -└───azure - └───sdk - └───models - │ _models.py # where the generated models are - | _patch.py # where we customize the models code -``` - -```python -from ._models import FooGenerated - -class Foo(FooGenerated): - """Foo. - - :param str input: Input. This is my new nicer description - """ - -__all__ = ["Foo"] -``` - ### Change Model Behavior To override model behavior, you will work with the `_patch.py` file in the `models` folder of your generated code. From 8b76242c8722a7c02da8f2ed53b4f111b5404bc4 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Wed, 26 Jan 2022 14:56:10 -0500 Subject: [PATCH 4/8] Update customizations.md --- docs/customizations.md | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index 82097e9b69d..577d232e312 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -14,7 +14,7 @@ generated code to override. The main flow of the `_patch.py` file will be: -1. Import the generated object you wish to override. Generated objects will have the suffix `Generated` +1. Import the generated object you wish to override. 2. Inherit from the generated object, and override its behavior with your desired code functionality 3. Include the name of your customized object in the `__all__` of the `_patch.py` file @@ -28,7 +28,6 @@ lost! ## Examples - [Change Model Behavior](#change-model-behavior) -- [Add a model](#add-a-model) - [Change Operation Behavior](#change-operation-behavior) - [Overload an Operation](#overload-an-operation) - [Change Client Behavior](#change-client-behavior) @@ -57,7 +56,7 @@ azure-sdk ```python import datetime from typing import Union -from ._models import ModelGenerated +from ._models import Model as ModelGenerated class Model(ModelGenerated): @@ -69,32 +68,6 @@ class Model(ModelGenerated): __all__ = ["Model"] ``` -### Add a Model - -You can add a model easily in the `_patch.py` file in the models module. Just be sure to include it in that file's `__all__`! - -In this `_patch.py` file: - -``` -azure-sdk -│ README.md -│ -└───azure - └───sdk - └───models - │ _models.py # where the generated models are - | _patch.py # where we customize the models code -``` - -```python -class AddedModel: - - def __init__(self, input: str): - self.input = input - -__all__ = ["AddedModel"] -``` - ### Change Operation Behavior To change an operation, you will import the generated operation group the operation is on. Then you can inherit @@ -119,7 +92,7 @@ azure-sdk ```python from typing import Union import datetime -from ._operations import OperationGroupGenerated +from ._operations import OperationGroup as OperationGroupGenerated class OperationGroup(OperationGroupGenerated): @@ -154,7 +127,7 @@ azure-sdk ```python from typing import overload, Dict, Any -from ._operations import OperationGroupGenerated +from ._operations import OperationGroup as OperationGroupGenerated class OperationGroup(OperationGroupGenerated): @@ -194,7 +167,7 @@ azure-sdk ``` ```python -from ._service_client import ServiceClientGenerated +from ._service_client import ServiceClient as ServiceClientGenerated from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import SansIOHTTPPolicy @@ -244,7 +217,7 @@ azure-sdk ```python from typing import Any from azure.core.credentials import AzureKeyCredential -from ._service_client import ServiceClientGenerated +from ._service_client import ServiceClient as ServiceClientGenerated class ServiceClient(ServiceClientGenerated): From 7f1d1328300a1bbd428f6106b89ef877f7db93ea Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Wed, 9 Mar 2022 16:52:18 -0500 Subject: [PATCH 5/8] modules -> subfolders --- docs/customizations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index 577d232e312..f2228ace83a 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -7,7 +7,7 @@ OpenAPI definition of your service. If you absolutely can not modify the OpenAPI ## Key Concept: _patch.py -The `_patch.py` files at each level of the modules will be the entry point to customize the generated code. +The `_patch.py` files at each level of the subfolders will be the entry point to customize the generated code. For example, if you want to override a model, you will use the `_patch.py` file at the `models` level of your generated code to override. @@ -110,7 +110,7 @@ __all__ = ["OperationGroup"] You can also easily overload generated operations. For example, if you want users to be able to pass in the body parameter as a positional-only single dictionary, or as splatted keyword arguments, you can inherit and override the operation on the operation group -in the `_patch.py` file in the `operations` modules. +in the `_patch.py` file in the `operations` subfolders. In this `_patch.py` file: From 027a1707af1ffea6fa913d7b6bef37a3fbfcb780 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Wed, 9 Mar 2022 17:36:18 -0500 Subject: [PATCH 6/8] Update customizations.md --- docs/customizations.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/customizations.md b/docs/customizations.md index f2228ace83a..4f20a6f792b 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -231,3 +231,7 @@ class ServiceClient(ServiceClientGenerated): __all__ = ["ServiceClient"] ``` + +### If all else fails... + +For a last resort escape hatch, we also have `patch_sdk` functions defined in every patch file. See [this document](https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/customize_code/how-to-patch-sdk-code.md) to find out more about them From 6d1e11685d5de784043da629227b1c9e743178c4 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:33:34 -0500 Subject: [PATCH 7/8] remove patch_sdk doc --- docs/customizations.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index 4f20a6f792b..f2228ace83a 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -231,7 +231,3 @@ class ServiceClient(ServiceClientGenerated): __all__ = ["ServiceClient"] ``` - -### If all else fails... - -For a last resort escape hatch, we also have `patch_sdk` functions defined in every patch file. See [this document](https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/customize_code/how-to-patch-sdk-code.md) to find out more about them From 6840fc179a0dfce985070d04068e2fda6b2cb12d Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:56:33 -0400 Subject: [PATCH 8/8] fix descriptions of patch file in folder hierarchy --- docs/customizations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/customizations.md b/docs/customizations.md index f2228ace83a..e3c7e3cd988 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -86,7 +86,7 @@ azure-sdk └───sdk └───operations │ _operations.py # where the generated operations are - | _patch.py # where we customize the models code + | _patch.py # where we customize the operations code ``` ```python @@ -122,7 +122,7 @@ azure-sdk └───sdk └───operations │ _operations.py # where the generated operations are - | _patch.py # where we customize the models code + | _patch.py # where we customize the operations code ``` ```python @@ -161,7 +161,7 @@ azure-sdk └───azure └───sdk │ _service_client.py # where the generated service client is - | _patch.py # where we customize the models code + | _patch.py # where we customize the client code └───operations └───models ``` @@ -209,7 +209,7 @@ azure-sdk └───azure └───sdk │ _service_client.py # where the generated service client is - | _patch.py # where we customize the models code + | _patch.py # where we customize the client code └───operations └───models ```