Initial commit
This commit is contained in:
commit
87c81fd7c0
143
.gitignore
vendored
Normal file
143
.gitignore
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
# IDE
|
||||
.idea
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Custom files
|
||||
*ready
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
24
.pre-commit-config.yaml
Normal file
24
.pre-commit-config.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
args:
|
||||
- --profile=black
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
types: [ python ]
|
||||
- repo: https://github.com/myint/autoflake
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: autoflake
|
||||
language_version: python3
|
||||
types: [ python ]
|
||||
args:
|
||||
- --remove-all-unused-imports
|
||||
- --recursive
|
||||
- --in-place
|
43
app/api.py
Normal file
43
app/api.py
Normal file
@ -0,0 +1,43 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel, Field, condecimal, conint, constr, root_validator
|
||||
|
||||
from app.types import Order, OrderSide, OrderType
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class CreateOrderModel(BaseModel):
|
||||
type_: OrderType = Field(..., alias="type")
|
||||
side: OrderSide
|
||||
instrument: constr(min_length=12, max_length=12)
|
||||
limit_price: Optional[condecimal(decimal_places=2)]
|
||||
quantity: conint(gt=0)
|
||||
|
||||
@root_validator
|
||||
def validator(cls, values: dict):
|
||||
if values.get("type_") == "market" and values.get("limit_price"):
|
||||
raise ValueError(
|
||||
"Providing a `limit_price` is prohibited for type `market`"
|
||||
)
|
||||
|
||||
if values.get("type_") == "limit" and not values.get("limit_price"):
|
||||
raise ValueError("Attribute `limit_price` is required for type `limit`")
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class CreateOrderResponseModel(Order):
|
||||
pass
|
||||
|
||||
|
||||
@app.post(
|
||||
"/orders",
|
||||
status_code=201,
|
||||
response_model=CreateOrderResponseModel,
|
||||
response_model_by_alias=True,
|
||||
)
|
||||
async def create_order(model: CreateOrderModel):
|
||||
# TODO: Add your implementation here
|
||||
raise NotImplementedError()
|
24
app/stock_exchange.py
Normal file
24
app/stock_exchange.py
Normal file
@ -0,0 +1,24 @@
|
||||
import random
|
||||
import time
|
||||
|
||||
from app.types import Order
|
||||
|
||||
|
||||
class OrderPlacementError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def place_order(order: Order):
|
||||
"""dummy function that is symbolic standing for placing an order at the stock exchange.
|
||||
Please do not modify the content of this method."""
|
||||
|
||||
if not order:
|
||||
raise ValueError("Required order parameter not provided")
|
||||
|
||||
if random.random() >= 0.9:
|
||||
raise OrderPlacementError(
|
||||
"Failed to place order at stock exchange. Connection not available"
|
||||
)
|
||||
|
||||
# it is an expensive operation
|
||||
time.sleep(0.5)
|
27
app/types.py
Normal file
27
app/types.py
Normal file
@ -0,0 +1,27 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, condecimal, conint, constr
|
||||
|
||||
|
||||
class OrderSide(Enum):
|
||||
BUY = "buy"
|
||||
SELL = "sell"
|
||||
|
||||
|
||||
class OrderType(Enum):
|
||||
MARKET = "market"
|
||||
LIMIT = "limit"
|
||||
|
||||
|
||||
class Order(BaseModel):
|
||||
# id generated by the database
|
||||
id_: str = Field(..., alias="id")
|
||||
created_at: datetime
|
||||
|
||||
type_: OrderType = Field(..., alias="type")
|
||||
side: OrderSide
|
||||
instrument: constr(min_length=12, max_length=12)
|
||||
limit_price: Optional[condecimal(decimal_places=2)]
|
||||
quantity: conint(gt=0)
|
50
readme.md
Normal file
50
readme.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Backend engineering task
|
||||
|
||||
### Intro
|
||||
|
||||
The repository contains a bare setup for a REST API to place orders via a `POST - /orders` request, it can be found in [./app/api.py](./app/api.py).
|
||||
Additionally, it contains [a building block](./app/stock_exchange.py) that symbolizes placing an order at the stock exchange.
|
||||
|
||||
The folder [tests](./tests) contains the setup for tests with the framework pytest.
|
||||
|
||||
The challenge in this repository is a simplified version of what we are dealing with at lemon.markets 🍋.
|
||||
|
||||
## Your task
|
||||
|
||||
We would like you to finalize the missing parts in this repository so that the following requirements are fulfilled:
|
||||
1. A valid request to `POST /orders` should result in the order being stored in a database of your choice.
|
||||
2. The created order should be placed at the stock exchange (use the method `place_order` provided in stock_exchange.py).
|
||||
3. The endpoint should return a status code of 201 and the created order details, provided that the order has been saved in the database **AND** it is guaranteed that the order **will** be placed on the stock exchange.
|
||||
4. In the case of an error in the endpoint, it should return the status code 500 and the body `{"message": "Internal server error while placing the order"}`
|
||||
5. The API should be highly scalable and reliable. The reliability of the provided stock exchange should not impact the reliability of the `POST /orders` endpoint
|
||||
|
||||
|
||||
Additionally, please add some tests and document how you would test the application as a whole and its pieces.
|
||||
For the implementation you can choose to use this Python setup or implement it in Javascript/Typescript.
|
||||
|
||||
Please include a `solution.md` file where you document your decisions, assumptions, and also improvements you would like to incorporate in the future.
|
||||
|
||||
We value your time ⏰, so we do not expect you to spend more than 4 hours preparing the solution. 🤗
|
||||
Focus on implementing the main task first and afterwards jump on additional improvements as you see fit.
|
||||
|
||||
### Bonus tasks
|
||||
* How would you change the system if we would receive a high volume of async updates to the orders placed through a socket connection on the stock exchange, e.g. execution information? Please outline the changes in the `solution.md`.
|
||||
* Feel free to add a GitHub actions workflow to test the application.
|
||||
* Feel free to add a Dockerfile.
|
||||
|
||||
## Code quality
|
||||
|
||||
In order to maintain a certain amount of code quality, we are using pre-commit hooks
|
||||
in this repository, which are installed by a 3rd-party tool called [pre-commit](https://pre-commit.com/).
|
||||
|
||||
Please follow [this documentation](https://pre-commit.com/#install) to install `pre-commit`
|
||||
on your local machine. After that just execute the following command to install the hooks
|
||||
to your git folder:
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
|
||||
### Everything process related
|
||||
Please create a fork or clone of this repository, commit all of your work to a new branch, and provide us with a link to the solution via mail 📩, at least 6h before the review meeting.
|
5
requirements-dev.txt
Normal file
5
requirements-dev.txt
Normal file
@ -0,0 +1,5 @@
|
||||
-r requirements.txt
|
||||
pytest
|
||||
pytest-cov
|
||||
pre-commit
|
||||
requests
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
fastapi==0.85.0
|
||||
uvicorn[standard]==0.18.3
|
||||
pydantic==1.10.2
|
9
tests/conftest.py
Normal file
9
tests/conftest.py
Normal file
@ -0,0 +1,9 @@
|
||||
import pytest as pytest
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from app.api import app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client() -> TestClient:
|
||||
return TestClient(app)
|
Loading…
x
Reference in New Issue
Block a user