Internals
Decorators¶
Within PSEngine we use a series of decorators to make the developer’s life easier. Some are built in psengine.helpers
, others come from different libraries.
We use these decorators on user-facing (public) methods only, unless in rare circumstances.
validate_call
decorator¶
This decorator (implemented by pydantic
) validates the method’s parameters and output with the type hints supplied and raises a ValidationError
if something is called with the wrong type. This means there is no longer a necessity to check the instance type of parameters in the code. For example, the below code is not needed anymore and can be shortened with this decorator:
Rewritten with the validate_call
decorator:
debug_call
decorator¶
This decorator logs the input and output of a method at debug level. It is built in to psengine
.
connection_exceptions
decorator¶
This decorator is built in to psengine
and handles HTTP-related exceptions.
It helps you avoid having to remember all the HTTP exceptions that a request can raise.
Apply it to a method and specify the custom exception to raise instead.
For example, here we raise a NewFeatureLookupError
for any request that does not return HTTP 2xx or 404.
In case of a 200 the response is returned; in case of a 404, None
is returned.
total_ordering
This decorator comes from the functools
module in the standard library. It dynamically defines the ordering operations given at least two predefined ones. You only need to define __eq__
and __gt__
methods in a class and the decorator implements the others.
We use this decorator on top of ADTs (see more on this below) when it makes sense, based on attributes suitable for ordering.
Pydantic¶
With pydantic
we create models representing the data coming from the API. This lets us know that once we get the data back, we are sure they are of the type we expect, avoiding the need for most validation in your code. For example, if we get the risk score of an IOC back, it is validated as int
; we don’t have to check if the risk score is an integer in the code.
A model looks like this:
Here we say that risk_score
and risk_rule
are Optional
, meaning they might not be present in the payload, but if they are present they have to be strings. The default value is None
but it is not what is assigned to the parameter. The only required parameter is ioc
, which represents a string.
We use RFBaseModel
, which is a pydantic.BaseModel
with a few additional settings. Each model should inherit from it. Models can have other models as validators. For example, below the risk_rule
parameter is a list of RiskRule
models.
Some fields in the Recorded Future API use JSON naming conventions, for example IpAddress
. Since pydantic
needs field names mapped correctly to the incoming payload for validation, to validate the JSON below:
We need a pydantic
model with a
and b
as fields:
However, Python naming conventions for variables and parameters differ from JSON. A field like IpAddress
in Python would be ip_address
; hence pydantic
allows the use of an alias
:
Sometimes the Recorded Future API has constraints that cannot be easily expressed via pydantic
. For example, in the Detection Rule API if the detection type is detection_rule
we must have the id
and sub_type
fields. This can be defined with a @model_validator
decorator of pydantic
. It allows you to define a function to execute before or after (based on the mode
) the pydantic
validation.
ADT¶
ADT (Abstract Data Type) models are used by the manager class and the end user. The difference between an ADT and a pydantic
model is that, even though an ADT is a pydantic
model, it has additional features, for example special methods (__str__
, __eq__
, etc.) and properties.
A pydantic
model used by the user represents the payload sent to or returned by an API endpoint. For example, a POST endpoint can receive a pydantic
model as input and return a pydantic
model as output. As such, those models have a suffix of In
or Out
based on the direction (In to the API, Out of the API).
Naming Convention¶
We use a naming convention for classes:
- Manager is called
<ADT>Mgr
. - The ADT is named after the object it represents. For example, the ADT of an Analyst Note is
AnalystNote
. pydantic
models used by users that are not ADTs are suffixed withIn
orOut
. For example, thePreview
payload to be sent to the/analystnote/preview
endpoint can be calledAnalystNotePreviewIn
.