Skip to content

Commit

Permalink
The example has been moved from another repo.
Browse files Browse the repository at this point in the history
  • Loading branch information
sergzach committed May 12, 2019
0 parents commit 0d29329
Show file tree
Hide file tree
Showing 13 changed files with 750 additions and 0 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Task Description
Write client-server application.

Use case: application which is used in car centers to store information
about sold cars. At first application will be used in a single car center then
all centers in town/city/world will use it to store information. It should be
possible to retrieve info about any sold car by serialNumber.
Clients pass to server an Object “Car” with following attributes:
```
"ownerName" type="string"
"serialNumber" type="uint64"
"modelYear" type="uint64"
"code" type="string"
"vehicleCode" type="string"
"engine"
"capacity" type="uint16"
"numCylinders" type="uint8"
"maxRpm" type="uint16"
"manufacturerCode" type="char"
"fuelFigures"
"speed" type="uint16"
"mpg" type="float"
"usageDescription" type="string"
"performanceFigures"
"octaneRating" type="uint16"
"acceleration"
"mph" type="uint16"
"seconds" type="float"
"manufacturer" type="string" "model" type="string"
"activationCode" type="string"
```
Server saves the information persistently.


# How to start carstore_app test
* Setup requirements from src/: `pip3 install -r requirements.txt`.
* Setup carserver.py from src/: `python3 setup.py install`.
* Add environment variables from run/env.txt. Fix ports to carserver.py/PostgreSQL for other settings.
* Run run/server.sh. Fix the options if mentioned ports are unavailable.
* Run run/test_client.sh. Fix the options if mentioned ports are unavailable.


# Source files
* src/server.py — a socket server which can pick up any response module (an application).
* src/capstore_app.py — a particular application which have on_response() calling from server.py.
* src/capstore_model.py — a model (for PostgreSQL) to store the information about cars.
* src/test_client.py — a program with client which make simple requests to server and prints the server answer.
* src/create_db.py — code to create database (PostgreSQL) schema. Just for an intermediate test.


# Notes
A Python file with an application must be located in the same directory where carserver.py script starts. For more details see `run/server.sh` please.
Empty file added app/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions app/carstore_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
A response module for car store.
"""
import json
from carstore_model import get_car, save_car, delete_car

_FIELD_ACTION = 'action'
_FIELD_CAR_DATA = 'car_data'
_FIELD_SERIAL_NUMBER = 'serial_number'
_ACTION_GET = 'get'
_ACTION_SAVE = 'save'
_ACTION_DELETE = 'delete'


class ResponseFormatError(Exception):
"""
It raises when format of client request is wrong.
"""
pass


def on_response(data):
"""
A response function for carstore application.
"""
data = json.loads(data)
action = data[_FIELD_ACTION]
if action == _ACTION_GET:
serial_number = int(data[_FIELD_SERIAL_NUMBER])
car_data = get_car(serial_number=serial_number)
return car_data
elif action == _ACTION_SAVE:
car_data = data[_FIELD_CAR_DATA]
car_data[_FIELD_SERIAL_NUMBER] = data[_FIELD_SERIAL_NUMBER]
save_car(car_data)
return True
elif action == _ACTION_DELETE:
serial_number = int(data[_FIELD_SERIAL_NUMBER])
delete_car(serial_number=serial_number)
return True
else:
raise ResponseFormatError('Wrong data format from client.')
147 changes: 147 additions & 0 deletions app/carstore_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
A model for car store.
"""
import os
import decimal
from sqlalchemy import Column, CheckConstraint
from sqlalchemy.types import *
from sqlalchemy.dialects.postgresql.ranges import NUMRANGE
from sqlalchemy import create_engine
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.ext.declarative import declarative_base


_DBPG_HOST = os.environ['CARSTORE__DBPG_HOST']
_DBPG_PORT = os.environ['CARSTORE__DBPG_PORT']
_DBPG_NAME = os.environ['CARSTORE__DBPG_NAME']
_DBPG_USER = os.environ['CARSTORE__DBPG_USER']
_DBPG_PASSWORD = os.environ['CARSTORE__DBPG_PASSWORD']


class UINTConstraintError(Exception):
pass


def _constraint_uint(name, num_bits):
"""
To add constraint for uint64/32/16/8.
"""
if num_bits not in [8, 16, 32, 64]:
raise UINTConstraintError('Wrong number of bits.')
return CheckConstraint( '{name} >= 0 AND {name} < {max_num}'.format(name=name, max_num = 2 ** num_bits),
name='{}_positive'.format(name))


Base = declarative_base()


class Car(Base):
"""
A car model.
"""

__tablename__ = 'car'
# center where car is located
serial_number = Column(NUMERIC, nullable=False, primary_key=True)
location_center_id = Column(NUMERIC, index=True)
owner_name = Column(String)
model_year = Column(NUMERIC)
code = Column(String)
vehicle_code = Column(String)
engine__capacity = Column(Integer)
engine__num_cylinders = Column(SmallInteger)
fuel_figures__speed = Column(Integer)
fuel_figures__mpg = Column(Float)
fuel_figures__usage_description = Column(String)
performance_figures__octane_rating = Column(Integer)
performance_figures__acceleration__mph = Column(Integer)
performance_figures__acceleration__seconds = Column(Float)
manufacturer = Column(String)
__table_args__ = (
_constraint_uint('location_center_id', 32),
_constraint_uint('serial_number', 64),
_constraint_uint('model_year', 64),
_constraint_uint('engine__capacity', 16),
_constraint_uint('engine__num_cylinders', 8),
_constraint_uint('fuel_figures__speed', 16),
_constraint_uint('performance_figures__octane_rating', 16),
_constraint_uint('performance_figures__acceleration__mph', 16)
)


def get_engine():
"""
Get SQLAlchemy engine for car DB.
"""
connect_arg = 'postgresql://{user}:{password}@{host}:{port}/{name}'.format( \
user=_DBPG_USER,
password=_DBPG_PASSWORD,
host=_DBPG_HOST,
port=_DBPG_PORT,
name=_DBPG_NAME)

engine = create_engine(connect_arg)
return engine


def _get_session(*, autocommit=False):
"""
Get SQLAlchemy session for car DB.
"""
engine = get_engine()
Session = sessionmaker(bind=engine, autocommit=autocommit)
session = Session()
return session


def _change_json_types(d, supported_json_types, replace_types):
"""
Core function (without predifined arguments) for fixing model fields to make
it serializable.
"""
out_d = {}
for key, val in d.items():
type_of = type(val)
if type_of in supported_json_types:
replace_val = d[key]
if type_of in replace_types:
replace_val = replace_types[type_of](replace_val)
out_d[key] = replace_val

return out_d


def _as_json_types(d):
"""
Fixing (changling/removing) model fields to make it serializable.
"""
return _change_json_types(d, (decimal.Decimal, float, int, str), {decimal.Decimal: int})


def get_car(*, serial_number):
"""
Getting a car data by it's serial number.
"""
session = _get_session()
car_data = session.query(Car).filter_by(serial_number=serial_number).scalar()
result = _as_json_types(car_data.__dict__) if car_data is not None else None
return result


def save_car(car_data):
"""
Saving the information about a car. All fields are required.
"""
session = _get_session(autocommit=True)
car = Car(**car_data)
session.merge(car)
session.flush()


def delete_car(*, serial_number):
"""
Delete a car by a serial number (it is used by test).
"""
session = _get_session(autocommit=True)
session.query(Car).filter_by(serial_number=serial_number).delete()
session.flush()
7 changes: 7 additions & 0 deletions run/evn.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CARSTORE__DBPG_HOST=localhost
CARSTORE__DBPG_PORT=5432
CARSTORE__DBPG_NAME=carstore
CARSTORE__DBPG_USER=user
CARSTORE__DBPG_PASSWORD=password
CARSTORE_HOST=localhost
CARSTORE_PORT=8000
7 changes: 7 additions & 0 deletions run/server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

CUR_DIR=$(dirname "$0")

cd $CUR_DIR/../app && \
carserver.py --max-connections=32 --buff-size=4096 \
--response-module=carstore_app --max-read-time=2000 --poll_timeout=1
5 changes: 5 additions & 0 deletions run/test_client.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

CUR_DIR=$(dirname "$0")

cd $CUR_DIR/../src && python3 -m pytest test_client.py
Loading

0 comments on commit 0d29329

Please sign in to comment.