-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The example has been moved from another repo.
- Loading branch information
0 parents
commit 0d29329
Showing
13 changed files
with
750 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.