Romani Mind, v1.0
Posted on

Web service for a handling of a business logic of conversations.
Major release. Load a handler class from a specified file and simplify the interface of a handling.
Change Log
- Handler:
- Load a handler class from the specified file:
- support a loading from any place;
- reload the handler class on each request.
- Split a request handling to a start of a conversation and a processing of new messages.
- Receive a default state of a conversation from the handler class.
- Add an abstract class for the handler class.
- Load a handler class from the specified file:
- Request:
- Fix the JSON Schema of a request:
- Use an integer type for timestamp fields.
- Check that all integer fields are positive.
- Transform a request from a dictionary to an object.
- Pass a request to a handler by top level parts.
- Reverse an order of messages in a request.
- Parse timestamps in a request.
- Add a stuff for the Postman tool.
- Fix the JSON Schema of a request:
- Response:
- Combine a response from top level parts returned by a handler.
- Automatically detect types of messages based on their content.
Features
- working mode:
- run as a web service;
- control via the RESTful API;
- handle a conversation by the specified outer handler class (see below for details);
- commands:
- handle a part of a history of a thread with an user:
- request data:
- list of messages;
- user info;
- arbitrary auxiliary data;
- response data:
- list of messages;
- probably updated arbitrary auxiliary data;
- request data:
- handle a part of a history of a thread with an user:
- handler:
- load the handler class (see below for details) from the specified file:
- support a loading from any place;
- reload the handler class on each request;
- split a request handling to a start of a conversation and a processing of new messages;
- receive a default arbitrary auxiliary data from the handler class (see below for details);
- request:
- pass a request to the handler by top level parts (an user info, an arbitrary auxiliary data and a list of messages);
- reverse an order of messages in a request;
- response:
- combine a response from top level parts returned by the handler (an arbitrary auxiliary data and a list of messages);
- automatically detect types of messages based on their content.
- load the handler class (see below for details) from the specified file:
Command
Command format in the JSON Schema format:
{
"type": "object",
"required": ["messages", "user", "state"],
"additionalProperties": false,
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "type", "timestamp", "text"],
"additionalProperties": false,
"properties": {
"id": {"$ref": "#/definitions/numeric_string"},
"type": {"$ref": "#/definitions/alphabetical_string"},
"timestamp": {"$ref": "#/definitions/positive_integer"},
"text": {"type": "string"}
}
}
},
"user": {
"type": "object",
"required": [
"id",
"category",
"username",
"full_name",
"gender",
"birthday",
"biography",
"location",
"counters",
"flags"
],
"additionalProperties": false,
"properties": {
"id": {"$ref": "#/definitions/numeric_string"},
"category": {"type": "string"},
"username": {"type": "string"},
"full_name": {"type": "string"},
"gender": {"$ref": "#/definitions/positive_integer"},
"birthday": {"type": "string"},
"biography": {"type": "string"},
"location": {
"type": "object",
"required": ["geo", "address"],
"additionalProperties": false,
"properties": {
"geo": {
"type": "object",
"required": ["latitude", "longitude"],
"additionalProperties": false,
"properties": {
"latitude": {"type": "number"},
"longitude": {"type": "number"}
}
},
"address": {
"type": "object",
"required": ["street", "city", "zip", "country_code"],
"additionalProperties": false,
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"zip": {"type": "string"},
"country_code": {"$ref": "#/definitions/positive_integer"}
}
}
}
},
"counters": {
"type": "object",
"required": ["media", "followers", "followings"],
"additionalProperties": false,
"properties": {
"media": {"$ref": "#/definitions/positive_integer"},
"followers": {"$ref": "#/definitions/positive_integer"},
"followings": {"$ref": "#/definitions/positive_integer"}
}
},
"flags": {
"type": "object",
"required": ["is_private", "is_business", "is_verified"],
"additionalProperties": false,
"properties": {
"is_private": {"type": "boolean"},
"is_business": {"type": "boolean"},
"is_verified": {"type": "boolean"}
}
}
}
},
"state": {}
},
"definitions": {
"positive_integer": {"type": "integer", "minimum": 0},
"numeric_string": {"type": "string", "pattern": "^\\d+$"},
"alphabetical_string": {"type": "string", "pattern": "^[a-z_]+$"}
}
}
Example:
{
"state": null,
"user": {
"id": "7580950755",
"category": "",
"username": "thewizardplusplus",
"full_name": "",
"gender": 0,
"birthday": "",
"biography": "",
"location": {
"geo": {
"latitude": 0,
"longitude": 0
},
"address": {
"street": "",
"city": "",
"zip": "",
"country_code": 0
}
},
"counters": {
"media": 3,
"followers": 0,
"followings": 0
},
"flags": {
"is_private": false,
"is_business": false,
"is_verified": false
}
},
"messages": [
{
"id": "28205430427220448756392777616457728",
"type": "media",
"timestamp": 1529019447254058,
"text": ""
},
{
"id": "28205430242905285891625733805047808",
"type": "text",
"timestamp": 1529019437262313,
"text": "Test two."
},
{
"id": "28205429958923490047994478895038464",
"type": "media",
"timestamp": 1529019421867629,
"text": ""
},
{
"id": "28205429795094598621958535854948352",
"type": "text",
"timestamp": 1529019412986447,
"text": "Test one."
}
]
}
API
API description in the Swagger format:
openapi: '3.0.0'
info:
title: romani-mind
version: 1.0.0
servers:
- url: http://localhost:{port}/api/v1
variables:
port:
default: '7070'
paths:
/history:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/request'
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/response'
400:
$ref: '#/components/responses/error'
404:
$ref: '#/components/responses/error'
405:
$ref: '#/components/responses/error'
500:
$ref: '#/components/responses/error'
components:
schemas:
positive_integer:
type: integer
minimum: 0
numeric_string:
type: string
pattern: ^\\d+$
alphabetical_string:
type: string
pattern: ^[a-z_]+$
request_message:
type: object
required:
- id
- type
- timestamp
- text
properties:
id:
$ref: '#/components/schemas/numeric_string'
type:
$ref: '#/components/schemas/alphabetical_string'
timestamp:
$ref: '#/components/schemas/positive_integer'
text:
type: string
geo:
type: object
required:
- latitude
- longitude
properties:
latitude:
type: number
longitude:
type: number
address:
type: object
required:
- street
- city
- zip
- country_code
properties:
street:
type: string
city:
type: string
zip:
type: string
country_code:
$ref: '#/components/schemas/positive_integer'
location:
type: object
required:
- geo
- address
properties:
geo:
$ref: '#/components/schemas/geo'
address:
$ref: '#/components/schemas/address'
user:
type: object
required:
- id
- category
- username
- full_name
- gender
- birthday
- biography
- location
- counters
- flags
properties:
id:
$ref: '#/components/schemas/numeric_string'
category:
type: string
username:
type: string
full_name:
type: string
gender:
$ref: '#/components/schemas/positive_integer'
birthday:
type: string
biography:
type: string
location:
$ref: '#/components/schemas/location'
counters:
type: object
required:
- media
- followers
- followings
properties:
media:
$ref: '#/components/schemas/positive_integer'
followers:
$ref: '#/components/schemas/positive_integer'
followings:
$ref: '#/components/schemas/positive_integer'
flags:
type: object
required:
- is_private
- is_business
- is_verified
properties:
is_private:
type: boolean
is_business:
type: boolean
is_verified:
type: boolean
request:
type: object
required:
- messages
- user
- state
properties:
messages:
type: array
items:
$ref: '#/components/schemas/request_message'
user:
$ref: '#/components/schemas/user'
state:
nullable: true
response_message:
type: object
oneOf:
- required:
- type
- message
properties:
type:
type: string
enum:
- message
message:
type: string
- required:
- type
- filename
properties:
type:
type: string
enum:
- photo
filename:
type: string
response:
type: object
required:
- messages
- state
properties:
messages:
type: array
items:
$ref: '#/components/schemas/response_message'
state:
nullable: true
responses:
error:
description: Some Error
content:
text/plain:
schema:
type: string
Architecture
UML component diagram in the PlantUML language:
@startuml
skinparam componentStyle uml2
[Thinker Class]
package "Romani Mind" {
[Web Server] -up- API
[Web Server] .down.> [History Handler] : history
[History Handler] .down.> [Thinker Class] : get_default_state()
[History Handler] .down.> [Thinker Class] : on_start()
[History Handler] .down.> [Thinker Class] : on_new_messages()
}
[Romani Core] .down.> API : POST /history
@enduml
Rendered UML component diagram:
Abstract Class for a Handler
User = NewType('User', NamedTuple)
JSON = Union[None, bool, int, float, str, Sequence['JSON'], Mapping[str, 'JSON']]
State = JSON
Message = NewType('Message', NamedTuple)
History = Sequence[Message]
Response = Tuple[State, Sequence[str]]
class Thinker(ABC):
def get_default_state(self) -> State:
return None
@abstractmethod
def on_start(self, user: User, state: State) -> Response:
pass
@abstractmethod
def on_new_messages(self, user: User, state: State, history: History) -> Response:
pass
Screenshots
POST /history

POST /history endpoint in the Postman tool with a start of a conversation

POST /history endpoint in the Postman tool with a request data from environment variables

POST /history endpoint in the Postman tool with the directly specified request data
