Tutorial - Getting Started¶
Basic Use-Case story¶
Another common problem is partially constructed instances. For example, when the developer forgot one of the fields. | These are just some of the problems with having a loose definition of the API, or data. In short - we end up with a brittle and unmaintainable code. | | Furthermore, typically every function/component validates the content of the parameters. This can result in a a lot | of repetitive boilerplate code, or inconsistencies in the expectations from the values of the properties. For example, Different | functions might expect different date format.
class Trade(ImmutableStructure):
notional: DecimalNumber(maximum=10000, minimum=0)
quantity: PositiveInt(maximum=100000, multiplesOf=5)
symbol: String(pattern='[A-Z]+$', maxLength=6)
denomination: Enum[Currency]
participant_buyer: Trader
participant_seller: Trader
timestamp: DateField
venue: Enum[Venue]
id: String
where:
class Trader(ImmutableStructure):
lei: String(pattern='[0-9A-Z]{18}[0-9]{2}$')
alias: String(maxLength=32)
class Venue(enum.Enum):
NYSE = enum.auto()
CBOT = enum.auto()
AMEX = enum.auto()
NASDAQ = enum.auto()
class Currency(enum.Enum):
USD = enum.auto()
GBP = enum.auto()
EUR = enum.auto()
Structure Validation¶
MAX_ALLOWED_TOTAL_VALUE = 10000000
class Trade(ImmutableStructure):
.... # no change
....
def __validate__(self):
if self.quantity * self.notional > MAX_ALLOWED_TOTAL_VALUE:
raise ValueError("trade value is too high")
if self.participant_seller == self.participant_buyer:
raise ValueError("buyer cannot be seller")
And that’s it.
Optionals¶
Next, we are asked to add an optional comments field to trade. This is done by updating the Trade structure as follows:
class Trade(ImmutableStructure):
.... # no change
....
comments: str
_optional = ['comments']
or, alternatively:
from typing import Optional
class Trade(ImmutableStructure):
.... # no change
....
comments: Optional[str]
Default value¶
A business analyst asks you to change the default value for comments from None to “nothing to see here”. You do it by change the declaration as follows:
class Trade(ImmutableStructure):
.... # no change
....
comments: str = "Nothing to see here..."
Note that once we declared an explicit default value to a field, it is already implicitly already optional, so there is no need to define it as such.
Serialization¶
You create a web service that allows to query for a trade by its ID. You could write code to convert the Trade instance to a serializable dictionary. But Typepy offers a simpler way:
output = Serializer(trade).serialize()
Next, you realize that that the client expects “buyer” instead of “participant_buyer”, and “seller” instead of “participant_seller”. Let’s do it:
mapper = {'participant_buyer': 'buyer',
'participant_seller': 'seller'}
output = Serializer(trade, mapper=mapper).serialize()
Deserialization¶
Next, you need to create an endpoint that allows to post trades. Instead of writing a lengthy validation you can just create the following code:
class BadRequestException(Exception): pass
try:
Deserializer(Trade).deserializer(request.json)
except Exception as e:
BadRequestException(e)
Let’s say that our web service uses Flask, then you may create an error handle:
def set_error_handlers(app: Flask):
@app.errorhandler(BadRequestException)
def invalid_request():
return f'invalid request: {e}', HTTPStatus.BAD_REQUEST
This is nice, as it eliminates all the validation code, and the potential of impedance mismatch between it and the definition of what constitutes a valid Trade.
Mapping to a JSON schema¶
Another team builds a client of your service is Java. They are asking for a JSON or OpenAPI schema of your API. You can provide create one with the following code:
schema, definitions = structure_to_schema(Trade, definitions_schema, {})
The definitions variable above is the schema of objects that are used in the main schema, and are referred to from the main schema (e.g. : “$ref”: “#/definitions/Trader”). The schema is the main schema for Trade.