Python Business Transactions Library - ContractsPY
Declare and define business transactions in Python. Use the contracts library to validate business transactions with Railway-oriented approach.
Compatibility
Tested on Python 3.8+
Installation
There is no additional dependencies. Library's footprint is small.
>>> pip install contractsPY
Contract functions
Contract functions are simple python functions that return True or False. They are used to define business rules for transactions. They accept only one argument, which is the current state of the transaction.
def my_contract(state):
return state.a > state.b
if_fails decorator
You can use the if_fails decorator to define a message if the contract fails. This decorator
is optional. If you don't use it, the message will be the name of the contract function.
State object
All contract functions accept a single argument, which is the current state of the transaction.
State is the dictionary that contains all the data of the transaction. You can set and get
values from the state. For example: state.a = 10 or state.b = state.a + 1. Remember that, state keys are immutable. So, you can't change the state key-value pairs after you set them. For example, you can't do state.a = 10 and then state.a = 20.
Example (Create User):
Let's assume we have a user service that creates a new user. There are multiple steps to create a new user.
- First, we need to validate the user's data.
- Then, we need to make sure that the user doesn't already exist.
- We need to save the user to the database.
- We need to send an email to the user.
There are many ways to do this. We can use the contracts library to make it easier and more readable. All you need to do is to define a contract for each step. I can hear you saying "Why do I need to make things like this?". Well, I'm glad you asked.
- Railway-oriented approach:
As we mentioned before, the contractsPY library uses the Railway-oriented approach. This approach helps you to handle every possible scenario on the way to the final step. That's why they called it "Railway-oriented".
Whenever you call a function, there are two possible returns (success and failure). Your business logic is executed in the success case. If the business logic fails, you can handle the failure case.
from contractspy import if_fails, Usecase
@if_fails(message="Not validated.")
def validate_inputs(state):
if state.password and state.username:
return True
return False
@if_fails(message="User not generated.")
def generate_user(state):
state.user = User(state.username, state.password)
return True if state.user else False
@if_fails(message="User exists.")
def validate_user_exists(state):
for user in users:
if user.username == state.user.username:
return False
return True
@if_fails(message="User not saved.")
def persist_user(state):
users.append(state.user)
return True if state.user else False
register_user = Usecase()
register_user.contract = [
validate_inputs,
generate_user,
validate_user_exists,
persist_user
]
if __name__ == '__main__':
result = register_user.apply(username='johndoe', password='foobar')
print(result)
Result:
As you can see from the Result, user was not created. The reason is that the user already exists. Now, we can handle the failure case, and send proper error messages to the user.
>>> Result(state={'username': 'johndoe', 'password': 'foobar', 'user': User(username=johndoe, password=foobar)}, case=error, message='User exists.')
The result object contains three fields. State, case, message. You can check the case and message to see what went wrong. If everything went well, you can pick a value from the state dictionary.
result.state = {'username': 'johndoe', 'password': 'foobar', 'user': User(username=johndoe, password=foobar)}
result.case = error
result.message = 'User exists.'
If there was no failure, the result should have been like this:
>>> Result(state={'username': 'johndoe', 'password': 'foobar', 'user': User(username=johndoe, password=foobar)}, case=success, message='Ok')
(C) Arzu Hussein
Metadata
Release files for contractsPY 1.0.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| contractsPY-1.0.7.tar.gz | 5.0 kB | Without provenance | Details for contractsPY-1.0.7.tar.gz |
Release files / contractsPY-1.0.7.tar.gz
| Download URL | contractsPY-1.0.7.tar.gz |
|---|---|
| Size | 5.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb515e58193d3237822073b4f54cdcc0b61c68df2247e39c91c43ff74e2d2b17
|
|
BLAKE2b-256 checksum How to use checksums |
57069186c4e59b68c0c90bbc107a86990766ec4ee439e335f8fa8a5cff582483
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.11.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.12
|
Security for contractsPY 1.0.7
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.