Package: servicestack
ServiceStack'sAdd ServiceStack Referencefeature allows clients to generate Native Types from directly within PyCharm usingServiceStack IntelliJ Plugin- providing a simple way to give clients typed access to your ServiceStack Services.YouTube:youtu.be/WjbhfH45i5kFirst class development experiencePythonis one of the worlds most popular programming languages thanks to its ease of use and comprehensive libraries which sees it excels in many industries from education where it's often the first language taught in school to data science, machine learning and AI where it's often the dominant language used. To maximize the experience for calling ServiceStack APIs within these environments ServiceStack now supports Python as a 1st class Add ServiceStack Reference supported language which gives Python developers an end-to-end typed API for consuming ServiceStack APIs, complete with IDE integration inPyCharmas well asbuilt-in support in x dotnet toolto generate Python DTOs for a remote ServiceStack instance from a single command-line.Ideal idiomatic Typed Message-based APITo maximize the utility of Python DTOs and enable richer tooling support, Python DTOs are generated asdataclasseswith support forJSON serializationand annotated with Python 3type hints- that's invaluable when scaling large Python code-bases and greatly improves discoverability of a remote API. DTOs are also enriched with interface markers through Python's multiple inheritance support which enables its optimal end-to-end typed API:The Python DTOs andJsonServiceClientlibrary follow Python'sPEP 8's naming conventionsso they'll naturally fit into existing Python code bases. Here's a sample oftechstacks.iogenerated Python DTOs containing string and int Enums, an example AutoQuery and a standard Request & Response DTO showcasing the rich typing annotations and naming conventions used:classTechnologyTier(str,Enum):PROGRAMMING_LANGUAGE='ProgrammingLanguage'CLIENT='Client'HTTP='Http'SERVER='Server'DATA='Data'SOFTWARE_INFRASTRUCTURE='SoftwareInfrastructure'OPERATING_SYSTEM='OperatingSystem'HARDWARE_INFRASTRUCTURE='HardwareInfrastructure'THIRD_PARTY_SERVICES='ThirdPartyServices'classFrequency(IntEnum):DAILY=1WEEKLY=7MONTHLY=30QUARTERLY=90# @Route("/technology/search")@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassFindTechnologies(QueryDb2[Technology,TechnologyView],IReturn[QueryResponse[TechnologyView]],IGet):ids:Optional[List[int]]=Nonename:Optional[str]=Nonevendor_name:Optional[str]=Nonename_contains:Optional[str]=Nonevendor_name_contains:Optional[str]=Nonedescription_contains:Optional[str]=None# @Route("/orgs/{Id}", "PUT")@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassUpdateOrganization(IReturn[UpdateOrganizationResponse],IPut):id:int=0slug:Optional[str]=Nonename:Optional[str]=Nonedescription:Optional[str]=Nonecolor:Optional[str]=Nonetext_color:Optional[str]=Nonelink_color:Optional[str]=Nonebackground_color:Optional[str]=Nonebackground_url:Optional[str]=Nonelogo_url:Optional[str]=Nonehero_url:Optional[str]=Nonelang:Optional[str]=Nonedelete_posts_with_report_count:int=0disable_invites:Optional[bool]=Nonedefault_post_type:Optional[str]=Nonedefault_subscription_post_types:Optional[List[str]]=Nonepost_types:Optional[List[str]]=Nonemoderator_post_types:Optional[List[str]]=Nonetechnology_ids:Optional[List[int]]=None@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassUpdateOrganizationResponse:response_status:Optional[ResponseStatus]=NoneThe smart PythonJsonServiceClientavailable in theservicestackpip and conda packages enabling the same
productive, typed API development experience available in our other 1st-class supported client platforms.Usingdataclassesenables DTOs to be populated using a single constructor expression utilizing named parameters which together with the genericJsonServiceClientenables end-to-end typed API Requests in a single LOC:from.dtosimport*fromservicestackimportJsonServiceClientclient=JsonServiceClient("https://test.servicestack.net")response:HelloResponse=client.get(Hello(name="World"))TheHelloResponseoptional type hint doesn't change runtime behavior but enables static analysis tools and IDEs like PyCharm to provide rich intelli-sense and development time feedback.InstallationThe only requirements for Python apps to perform typed API Requests are the generated Python DTOs and the genericJsonServiceClientwhich can be installed globally or in a virtual Python environment usingPython's pip:$ pip install servicestackOr if preferred can be installed withconda:$ conda install conda-build.Add conda-forge as channel usingconda config --add channels conda-forgeOn root directory runconda build .PyCharm ServiceStack PluginPython developers ofPyCharmProfessional andFREE Community Editioncan get a simplified development experience for consuming ServiceStack Services by installing theServiceStack Pluginfrom the JetBrains Marketplace:Where you'll be able to right-click on a directory and click onServiceStack Referenceon the context menu:To launch theAdd Python ServiceStack Referencedialog where you can enter the remote URL of the ServiceStack endpoint you wish to call to generate the Typed Python DTOs for all APIs which by default will saved todtos.py:Then just import the DTOs andJsonServiceClientto be able to consume any of the remote ServiceStack APIs:from.dtosimport*fromservicestackimportJsonServiceClientclient=JsonServiceClient("https://techstacks.io")response=client.send(FindTechnologies(ids=[1,2,4,6],vendor_name="Google",take=10))If any of the the remote APIs change their DTOs can be updated by right-clicking ondtos.pyand clickingUpdate ServiceStack Reference:Simple command-line utility for PythonDevelopers using other Python IDEs and Text Editors like VS Code can utilize the cross-platformxcommand line utilityfor generating Python DTOs from the command-line.To install first install thelatest .NET SDKfor your OS then install thexdotnet toolwith:$ dotnet tool install --global xAdding a ServiceStack ReferenceTo Add a Python ServiceStack Reference just callx pythonwith the URL of a remote ServiceStack instance:$ x python https://techstacks.ioResult:Saved to: dtos.pyCallingx pythonwith just a URL will save the DTOs using the Host name, you can override this by specifying a FileName as the 2nd argument:$ x python https://techstacks.io TechResult:Saved to: Tech.dtos.pyUpdating a ServiceStack ReferenceTo Update an existing ServiceStack Reference, callx pythonwith the Filename:$ x python dtos.pyResult:Updated: dtos.pyWhich will update the File with the latest Python Server DTOs fromtechstacks.io. You can also customize how DTOs are generated by uncommenting thePython DTO Customization Optionsand updating them again.Updating all Python DTOsCallingx pythonwithout any arguments will update all Python DTOs in the current directory:$ x pythonResult:Updated: Tech.dtos.py
Updated: dtos.pySmart Generic JsonServiceClientThe genericJsonServiceClientis a 1st class client with the same rich featureset of the smart ServiceClients in other1st class supported languagessporting a terse, typed flexible API with support for additional untyped params, custom URLs and HTTP Methods, dynamic response types including consuming API responses in raw text and binary data formats. Clients can be decorated to support generic functionality using instance and static Request, Response and Exception Filters.It includes built-in support for a number ofServiceStack Auth optionsincludingHTTP Basic Authand stateless Bearer Token Auth Providers likeAPI KeyandJWT Authas well asstateful Sessionsused by the popularcredentialsAuth Provider and anon_authentication_requiredcallback for enabling custom authentication methods. The built-in auth options include auto-retry support for transparently authenticating and retrying authentication required requests as well asRefresh Token Cookiesupport where it will transparently fetch new JWT Bearer Tokens automatically behind-the-scenes for friction-less stateless JWT support.A snapshot of these above features is captured in the high-level public API below:classJsonServiceClient:base_url:strreply_base_url:stroneway_base_url:strheaders:Optional[Dict[str,str]]bearer_token:Optional[str]refresh_token:Optional[str]refresh_token_uri:Optional[str]username:Optional[str]password:Optional[str]on_authentication_required:Callable[[],None]global_request_filter:Callable[[SendContext],None]# staticrequest_filter:Callable[[SendContext],None]global_response_filter:Callable[[Response],None]# staticresponse_filter:Callable[[Response],None]exception_filter:Callable[[Response,Exception],None]global_exception_filter:Callable[[Response,Exception],None]def__init__(self,base_url)defset_credentials(self,username,password)@propertydeftoken_cookie(self)@propertydefrefresh_token_cookie(self)defget(self,request:IReturn[T],args:Dict[str,Any]=None)->Tdefpost(self,request:IReturn[T],body:Any=None,args:Dict[str,Any]=None)->Tdefput(self,request:IReturn[T],body:Any=None,args:Dict[str,Any]=None)->Tdefpatch(self,request:IReturn[T],body:Any=None,args:Dict[str,Any]=None)->Tdefdelete(self,request:IReturn[T],args:Dict[str,Any]=None)->Tdefoptions(self,request:IReturn[T],args:Dict[str,Any]=None)->Tdefhead(self,request:IReturn[T],args:Dict[str,Any]=None)->Tdefsend(self,request,method:Any=None,body:Any=None,args:Dict[str,Any]=None)defget_url(self,path:str,response_as:Type,args:Dict[str,Any]=None)defdelete_url(self,path:str,response_as:Type,args:Dict[str,Any]=None)defoptions_url(self,path:str,response_as:Type,args:Dict[str,Any]=None)defhead_url(self,path:str,response_as:Type,args:Dict[str,Any]=None)defpost_url(self,path:str,body:Any=None,response_as:Type=None,args:Dict[str,Any]=None)defput_url(self,path:str,body:Any=None,response_as:Type=None,args:Dict[str,Any]=None)defpatch_url(self,path:str,body:Any=None,response_as:Type=None,args:Dict[str,Any]=None)defsend_url(self,path:str,method:str=None,response_as:Type=None,body:Any=None,args:Dict[str,Any]=None)defsend_all(self,request_dtos:List[IReturn[T]])# Auto Batch Reply Requestsdefsend_all_oneway(self,request_dtos:list)# Auto Batch Oneway RequestsChange Default Server ConfigurationThe above defaults are also overridable on the ServiceStack Server by modifying the default config on theNativeTypesFeaturePlugin, e.g:varnativeTypes=this.GetPlugin<NativeTypesFeature>();nativeTypes.MetadataTypesConfig.AddResponseStatus=true;...Python specific functionality can be added byPythonGeneratorPythonGenerator.DefaultImports.Add("requests");Customize DTO Type generationAdditional Python specific customization can be statically configured likePreTypeFilter,InnerTypeFilter&PostTypeFilter(available in all languages) can be used to inject custom code in the generated DTOs output.Use thePreTypeFilterto generate source code before and after a Type definition, e.g. this will append theDecoratorclass decorator on non enum & interface types:PythonGenerator.PreTypeFilter=(sb,type)=>{if(!type.IsEnum.GetValueOrDefault()&&!type.IsInterface.GetValueOrDefault()){sb.AppendLine("@Decorator");}};TheInnerTypeFiltergets invoked just after the Type Definition which can be used to generate common members for all Types and interfaces, e.g:PythonGenerator.InnerTypeFilter=(sb,type)=>{sb.AppendLine("id:str = str(random.random())[2:]");};There's alsoPrePropertyFilter&PostPropertyFilterfor generating source before and after properties, e.g:PythonGenerator.PrePropertyFilter=(sb,prop,type)=>{if(prop.Name=="Id"){sb.AppendLine("@IsInt");}};Emit custom codeTo enable greater flexibility when generating complex Typed DTOs, you can use[Emit{Language}]attributes to generate code before each type or property.These attributes can be used to generate different attributes or annotations to enable client validation for different validation libraries in different languages, e.g:[EmitCode(Lang.Python, "# App User")][EmitPython("@Validate")]publicclassUser:IReturn<User>{[EmitPython("@IsNotEmpty", "@IsEmail")][EmitCode(Lang.Swift | Lang.Dart, new[]{"@isNotEmpty()","@isEmail()"})]publicstringEmail{get;set;}}Which will generate[EmitPython]code in Python DTOs:# App User@Validate@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassUser:@IsNotEmpty@IsEmailemail:Optional[str]=NoneWhilst the generic[EmitCode]attribute lets you emit the same code in multiple languages with the same syntax.Python Reference ExampleLets walk through a simple example to see how we can use ServiceStack's Python DTO annotations in our Python JsonServiceClient. Firstly we'll need to add a Python Reference to the remote ServiceStack Service byright-clickingon a project folder and clicking onServiceStack Reference...(as seen in the above screenshot).This will import the remote Services dtos into your local project which looks similar to:""" Options:Date: 2021-08-14 15:33:39Version: 5.111Tip: To override a DTO option, remove "//" prefix before updatingBaseUrl: https://techstacks.io#GlobalNamespace:#MakePropertiesOptional: False#AddServiceStackTypes: True#AddResponseStatus: False#AddImplicitVersion:#AddDescriptionAsComments: True#IncludeTypes:#ExcludeTypes:#DefaultImports: datetime,decimal,marshmallow.fields:*,servicestack:*,typing:*,dataclasses:dataclass/field,dataclasses_json:dataclass_json/LetterCase/Undefined/config,enum:Enum/IntEnum#DataClass:#DataClassJson:"""@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassGetTechnologyResponse:created:datetime.datetime=datetime.datetime(1,1,1)technology:Optional[Technology]=Nonetechnology_stacks:Optional[List[TechnologyStack]]=Noneresponse_status:Optional[ResponseStatus]=None# @Route("/technology/{Slug}")@dataclass_json(letter_case=LetterCase.CAMEL,undefined=Undefined.EXCLUDE)@dataclassclassGetTechnology(IReturn[GetTechnologyResponse],IRegisterStats,IGet):slug:Optional[str]=NoneIn keeping with idiomatic style of local.pysources, generated types are not wrapped within a module by default. This lets you reference the types you want directly using normal import destructuring syntax:from.dtosimportGetTechnology,GetTechnologyResponseOr import all Types into your preferred variable namespace with:from.dtosimport*request=GetTechnology()Making Typed API RequestsMaking API Requests in Python is the same as all otherServiceStack's Service Clientsby sending a populated Request DTO using aJsonServiceClientwhich returns typed Response DTO.So the only things we need to make any API Request is theJsonServiceClientfrom theservicestackpackage and any DTO's we're using from generated Python ServiceStack Reference, e.g:from.dtosimportGetTechnology,GetTechnologyResponsefromservicestackimportJsonServiceClientclient=JsonServiceClient("https://techstacks.io")request=GetTechnology()request.slug="ServiceStack"r:GetTechnologyResponse=client.get(request)# typed to GetTechnologyResponsetech=r.technology# typed to Technologyprint(f"{tech.name}by{tech.vendor_name}({tech.product_url})")print(f"`{tech.name}TechStacks:{r.technology_stacks}")Constructors InitializerAll Python Reference dataclass DTOs also implementsinitmaking them much nicer to populate using a constructor expression with named params syntax we're used to in C#, so instead of:request=Authenticate()request.provider="credentials"request.user_name=user_namerequest.password=passwordrequest.remember_me=remember_meresponse=client.post(request)You can populate DTOs with a single constructor expression without any loss of Python's Typing benefits:response=client.post(Authenticate(provider='credentials',user_name=user_name,password=password,remember_me=remember_me))Sending additional arguments with Typed API RequestsMany AutoQuery Services utilizeimplicit conventionsto query fields that aren't explicitly defined on AutoQuery Request DTOs, these can be queried by specifying additional arguments with the typed Request DTO, e.g:request=FindTechStacks()r:QueryResponse[TechnologyStackView]=client.get(request,args={"vendorName":"ServiceStack"})Making API Requests with URLsIn addition to making Typed API Requests you can also call Services using relative or absolute urls, e.g:client.get("/technology/ServiceStack",response_as=GetTechnologyResponse)client.get("https://techstacks.io/technology/ServiceStack",response_as=GetTechnologyResponse)# https://techstacks.io/technology?Slug=ServiceStackargs={"slug":"ServiceStack"}client.get("/technology",args=args,response_as=GetTechnologyResponse)as well as POST Request DTOs to custom urls:client.post_url("/custom-path",request,args={"slug":"ServiceStack"})client.post_url("http://example.org/custom-path",request)Raw Data ResponsesTheJsonServiceClientalso supports Raw Data responses likestringandbyte[]which also get a Typed API once declared on Request DTOs using theIReturn<T>marker:publicclassReturnString:IReturn<string>{}publicclassReturnBytes:IReturn<byte[]>{}Which can then be accessed as normal, with their Response typed to a JavaScriptstrorbytesfor rawbyte[]responses:str:str=client.get(ReturnString())data:bytes=client.get(ReturnBytes())Authenticating using Basic AuthBasic Auth support is implemented inJsonServiceClientand follows the same API made available in the C# Service Clients where theuserName/passwordproperties can be set individually, e.g:client=JsonServiceClient(baseUrl)client.username=userclient.password=passresponse=client.get(SecureRequest())Or useclient.set_credentials()to have them set both together.Authenticating using CredentialsAlternatively you can authenticate using userName/password credentials byadding a Python Referenceto your remote ServiceStack Instance and sending a populatedAuthenticateRequest DTO, e.g:request=Authenticate()request.provider="credentials"request.user_name=user_namerequest.password=passwordrequest.remember_me=trueresponse:AuthenticateResponse=client.post(request)This will populate theJsonServiceClientwithSession Cookieswhich will transparently be sent on subsequent requests to make authenticated requests.Authenticating using JWTUse thebearer_tokenproperty to Authenticate with aServiceStack JWT Providerusing a JWT Token:client.bearer_token=jwtAlternatively you can use just aRefresh Tokeninstead:client.refresh_token=refresh_tokenWhere the client will automatically fetch a new JWT Bearer Token using the Refresh Token for authenticated requests.Authenticating using an API KeyUse thebearer_tokenproperty to Authenticate with anAPI Key:client.bearer_token=api_keyTransparently handle 401 Unauthorized ResponsesIf the server returns a 401 Unauthorized Response either because the client was Unauthenticated or the
configured Bearer Token or API Key used had expired or was invalidated, you can useonAuthenticationRequiredcallback to re-configure the client before automatically retrying the original request, e.g:auth_client=JsonServiceClient(AUTH_URL)client.on_authentication_required=lambdac=client,a=auth_client:[a.set_credentials(username,password),client.set_bearer_token(cast(AuthenticateResponse,a.get(Authenticate())).bearer_token)]# Automatically retries requests returning 401 Responses with new bearerTokenresponse=client.get(Secured())Automatically refresh Access TokensWith theRefresh Token support in JWTyou can use therefresh_tokenproperty to instruct the Service Client to automatically fetch new JWT Tokens behind the scenes before automatically retrying failed requests due to invalid or expired JWTs, e.g:# Authenticate to get new Refresh Tokenauth_client=JsonServiceClient(AUTH_URL)auth_client.username=usernameauth_client.password=passwordauth_response=auth_client.get(Authenticate())# Configure client with RefreshTokenclient.refresh_token=auth_response.refresh_token# Call authenticated Services and clients will automatically retrieve new JWT Tokens as neededresponse=client.get(Secured())Use therefresh_token_uriproperty when refresh tokens need to be sent to a different ServiceStack Server, e.g:client.refresh_token=refresh_tokenclient.refresh_token_uri=AUTH_URL+"/access-token"DTO Customization OptionsIn most cases you'll just use the generated Python DTO's as-is, however you can further customize how the DTO's are generated by overriding the default options.The header in the generated DTO's show the different options Python native types support with their defaults. Default values are shown with the comment prefix of//. To override a value, remove the#and specify the value to the right of the:. Any uncommented value will be sent to the server to override any server defaults.The DTO comments allows for customizations for how DTOs are generated. The default options that were used to generate the DTO's are repeated in the header comments of the generated DTOs, options that are preceded by a Python comment#are defaults from the server, any uncommented value will be sent to the server
to override any server defaults.""" Options:Date: 2021-08-15 08:26:46Version: 5.111Tip: To override a DTO option, remove "#" prefix before updatingBaseUrl: https://techstacks.io#GlobalNamespace:#MakePropertiesOptional: False#AddServiceStackTypes: True#AddResponseStatus: False#AddImplicitVersion:#AddDescriptionAsComments: True#IncludeTypes:#ExcludeTypes:#DefaultImports: datetime,decimal,marshmallow.fields:*,servicestack:*,typing:*,dataclasses:dataclass/field,dataclasses_json:dataclass_json/LetterCase/Undefined/config,enum:Enum/IntEnum#DataClass:#DataClassJson:"""We'll go through and cover each of the above options to see how they affect the generated DTO's:Change Default Server ConfigurationThe above defaults are also overridable on the ServiceStack Server by modifying the default config on theNativeTypesFeaturePlugin, e.g://Server example in C#varnativeTypes=this.GetPlugin<NativeTypesFeature>();nativeTypes.MetadataTypesConfig.AddResponseStatus=true;...We'll go through and cover each of the above options to see how they affect the generated DTO's:GlobalNamespaceAs Python lacks the concept of namespaces this just emits a comment with the namespace name:# module dtosAddResponseStatusAutomatically add aresponse_statusproperty on all Response DTO's, regardless if it wasn't already defined:classGetTechnologyResponse:...response_status:Optional[ResponseStatus]=NoneAddImplicitVersionLets you specify the Version number to be automatically populated in all Request DTO's sent from the client:classGetTechnology(IReturn[GetTechnologyResponse],IRegisterStats,IGet):version:int=1...This lets you know what Version of the Service Contract that existing clients are using making it easy to implement ServiceStack'srecommended versioning strategy.IncludeTypesIs used as a Whitelist to specify only the types you would like to have code-generated:""" Options:
IncludeTypes: GetTechnology,GetTechnologyResponseWill only generateGetTechnologyandGetTechnologyResponseDTO's:classGetTechnologyResponse:...classGetTechnology:...Include Generic TypesUse .NET's Type Name to include Generic Types, i.e. the Type name separated by the backtick followed by the number of generic arguments, e.g:IncludeTypes: IReturn`1,MyPair`2Include Request DTO and its dependent typesYou can include a Request DTO and all its dependent types with a.*suffix on the Request DTO, e.g:""" Options:
IncludeTypes: GetTechnology.*Which will include theGetTechnologyRequest DTO, theGetTechnologyResponseResponse DTO and all Types that they both reference.Include All Types within a C# namespaceIf your DTOs are grouped into different namespaces they can be all included using the/*suffix, e.g:""" Options:
IncludeTypes: MyApp.ServiceModel.Admin/*This will include all DTOs within theMyApp.ServiceModel.AdminC# namespace.Include All Services in a Tag GroupServicesgrouped by Tagcan be used in theIncludeTypeswhere tags can be specified using braces in the format{tag}or{tag1,tag2,tag3}, e.g:""" Options:
IncludeTypes: {web,mobile}Or individually:""" Options:
IncludeTypes: {web},{mobile}ExcludeTypesIs used as a Blacklist to specify which types you would like excluded from being generated:""" Options:
ExcludeTypes: GetTechnology,GetTechnologyResponseWill excludeGetTechnologyandGetTechnologyResponseDTOs from being generated.DefaultImportsThemodule:Symbolsshort-hand syntax can be used for specifying additional imports in your generated Python DTO. There are 3 different syntaxes for specifying different Python imports:""" Options:...DefaultImports: datetime,typing:*,enum:Enum/IntEnum"""Which will generate the popular import form of:importdatetimefromtypingimport*fromenumimportEnum,IntEnumDataClassChange whatdataclassdecorator is used, e.g:""" Options:...DataClass: init=False"""Will decorate every DTO with:@dataclass(init=False)classGetTechnology(IReturn[GetTechnologyResponse],IRegisterStats,IGet):slug:Optional[str]=NoneDataClassJsonChange whatdataclass_jsondecorator is used, e.g:""" Options:...DataClassJson: letter_case=LetterCase.PASCAL"""Will decorate every DTO with:@dataclass_json(letter_case=LetterCase.PASCAL)classGetTechnology(IReturn[GetTechnologyResponse],IRegisterStats,IGet):slug:Optional[str]=NoneWhich will result in each type being serialized with PascalCase.Customize SerializationTheservicestackclient lib allows for flexible serialization customization where you can change how different .NET Types are serialized and deserialized into native Python types.To illustrate this we'll walk through how serialization of properties containing binary data to Base64 is implemented.First we specify the Python DTO generator to emitbytestype hint for the popular .NET binary data types:PythonGenerator.TypeAliases[typeof(byte[]).Name]="bytes";PythonGenerator.TypeAliases[typeof(Stream).Name]="bytes";In the Python app we can then specify the serializers and deserializers to use for deserializing properties with thebytesdata type which converts binary data to/from Base64:fromservicestackimportTypeConvertersdefto_bytearray(value:Optional[bytes]):ifvalueisNone:returnNonereturnbase64.b64encode(value).decode('ascii')deffrom_bytearray(base64str:Optional[str]):returnbase64.b64decode(base64str)TypeConverters.serializers[bytes]=to_bytearrayTypeConverters.deserializers[bytes]=from_bytearrayInspect UtilsTo help clients with inspecting API Responses theservicestacklibrary also includes a number of helpful utils to quickly visualizing API outputs.For a basic indented object graph you can usedumpto capture andprintdumpto print the output of any API Response, e.g:fromdataclassesimportdataclassfromdataclasses_jsonimportdataclass_json,UndefinedfromtypingimportOptionalfromservicestackimportprintdump,printtable@dataclass_json(undefined=Undefined.EXCLUDE)@dataclassclassGithubRepo:name:strdescription:Optional[str]=Nonehomepage:Optional[str]=Nonelang:Optional[str]=field(metadata=config(field_name="language"),default=None)watchers:Optional[int]=0forks:Optional[int]=0response=requests.get(f'https://api.github.com/orgs/python/repos')orgRepos=GithubRepo.schema().loads(response.text,many=True)orgRepos.sort(key=operator.attrgetter('watchers'),reverse=True)print(f'Top 3{orgName}Repos:')printdump(orgRepos[0:3])Output:Top 3 python Repos:
[
    {
        name: mypy,
        description: Optional static typing for Python 3 and 2 (PEP 484),
        homepage: http://www.mypy-lang.org/,
        lang: Python,
        watchers: 9638,
        forks: 1564
    },
    {
        name: peps,
        description: Python Enhancement Proposals,
        homepage: https://www.python.org/dev/peps/,
        lang: Python,
        watchers: 2459,
        forks: 921
    },
    {
        name: typeshed,
        description: Collection of library stubs for Python, with static types,
        homepage: ,
        lang: Python,
        watchers: 1942,
        forks: 972
    }
]For tabular resultsets you can usetableto capture andprinttableto print API resultsets in a human-friendly markdown table with an optionalheadersparameter to specify the order and columns to display, e.g:print(f'\nTop 10{orgName}Repos:')printtable(orgRepos[0:10],headers=['name','lang','watchers','forks'])Output:Top 10 python Repos:
+--------------+-----------+------------+---------+
| name         | lang      |   watchers |   forks |
|--------------+-----------+------------+---------|
| mypy         | Python    |       9638 |    1564 |
| peps         | Python    |       2459 |     921 |
| typeshed     | Python    |       1942 |     972 |
| pythondotorg | Python    |       1038 |     432 |
| asyncio      |           |        945 |     178 |
| typing       | Python    |        840 |     130 |
| raspberryio  | Python    |        217 |      38 |
| typed_ast    | C         |        171 |      43 |
| planet       | Python    |        100 |     145 |
| psf-salt     | SaltStack |         87 |      50 |
+--------------+-----------+------------+---------+Alternatively you can usehtmldumpto generate API responses in a HTML UI which is especially useful inPython Jupyter Notebooksto easily visualize API responses, e.g: