Skip to content

Commit

Permalink
Merge pull request #3 from Hack-The-Travel/develop
Browse files Browse the repository at this point in the history
Release v1.0.0 - Birth
  • Loading branch information
gurza authored Jul 2, 2018
2 parents 48274bf + 3f7340e commit 5c26fa6
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# general things to ignore
*.py[cod]
.pytest_cache/

conf.py

# Mr Developer
.idea/
Expand Down
16 changes: 16 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.. :changelog:
Release History
===============

1.0.0 (2018-07-02)
++++++++++++++++++

* Birth!


0.0.1 (2018-06-23)
++++++++++++++++++

* Frustration
* Conception
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
sirena-quota
============

It's the best way to control tickets quota in Sirena.
It's the best way to control tickets quota in Sirena.
Notice, these scripts works with Sirena Web Services (SWC), not with Sirena XML Gate.

Behold, the power of:
```
$ python checker.py
OTA.UT - ok
$ python informer.py
```


## Installation
First, initialize your virtual environment
```
$ virtualenv .ve
$ source .ve/bin/activate
```

Install dependencies
```
$ pip install -r requirements.txt
```

Setup configuration
```
$ cp conf.sample.py conf.py
$ vim conf.py
✨🎩✨
```

Finally, setup sqlite database
```
$ python manage.py setup
```
60 changes: 60 additions & 0 deletions checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
import re
import requests
import logging
from utils import db_execute
import conf


def extract_ticket_quota(ticket_quota_response):
matches = re.findall(r'<quota>(\d+)<\/quota>', ticket_quota_response)
return int(matches[0])


def get_ticket_quota(user, password, gateway='https://ws.sirena-travel.ru/swc-main/bookingService'):
"""Returns ticket quota for PoS (ППр).
This method calls `getTicketQuota` method of Sirena WS.
:param user: Sirena WS user with supervisor permission in the PoS.
:param password: user password.
:param gateway: (optional) Sirena WS gateway to call getTicketQuota method.
:return: ticket quota.
:rtype: int
"""
rq = '''<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ser="http://service.swc.comtech/">
<soapenv:Header/>
<soapenv:Body>
<ser:getTicketQuota>
<dynamicId>0</dynamicId>
</ser:getTicketQuota>
</soapenv:Body>
</soapenv:Envelope>'''
r = requests.post(gateway, auth=(user, password), data=rq)
r.raise_for_status()
return extract_ticket_quota(r.text)


def save_check(db_name, account_code, ticket_quota):
db_execute(
db_name,
'''INSERT INTO quota_check (account, quota)
VALUES ('{account}', {quota})
'''.format(account=account_code, quota=ticket_quota)
)


if __name__ == '__main__':
for code, account in conf.accounts.items():
try:
save_check(
conf.db_name,
code,
get_ticket_quota(account['user'], account['password'])
)
print('{:20} - ok'.format(code))
except Exception as e:
print('{:20} - error'.format(code))
logging.critical(e, exc_info=True)
19 changes: 19 additions & 0 deletions conf.sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
accounts = {
'OTA.TCH': {
'user': 'ota_grs101',
'password': 'secret',
},
'OTA.UT': {
'user': 'ota_grs102',
'password': 'newsecret',
},
}
sender = ('Anton Yakovlev', '[email protected]')
recipient_info = '[email protected]'
recipient_alert = '[email protected]'
db_name = 'db/quota.db'
smtp_gateway= 'email-smtp.eu-west-1.amazonaws.com'
smtp_port = 587
smtp_user = 'aws_ses_user'
smtp_password = 'aws_ses_user_password'
4 changes: 4 additions & 0 deletions db/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
118 changes: 118 additions & 0 deletions informer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import smtplib
import email.utils
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import time
from utils import db_execute
import conf


def prepare_body(accounts_info, sender_name):
"""Returns message body in text and html format.
Structure of accounts_info
>>> accounts_info = [{
>>> 'code': 'OTA.TCH',
>>> 'quota': 985,
>>> 'datetime': '2018-06-23 19:26:16 (MSK)',
>>> 'alert': False,
>>> }]
:param accounts_info: list, accounts info, see above description of element structure.
:param sender_name: str, name of sender used in signature.
:return: tuple of two strings, (body_text, body_html)
:rtype: tuple
"""
status_msg = ''
alert_accounts = list()
for account in accounts_info:
if account['alert']:
alert_accounts.append(account['code'])
status_msg += '{account} - {quota}, проверено {dt}\r\n'\
.format(account=account['code'], quota=account['quota'], dt=account['datetime'])
alert_msg = ''
if len(alert_accounts):
alert_msg = 'Срочно запросите квоту для: ' + ', '.join(alert_accounts) + '!\r\n\r\n'
body_text = (
'Приветствую\r\n\r\n'
'{alert}'
'Состояние стоков:\r\n{status_msg}'
'\r\n\r\n'
'--\r\n'
'Дружески,\r\n'
'{sender}'
)
body_html = (
'<html>'
'<head></head>'
'<body>'
'<p>Приветствую</p>'
'{alert}'
'<p>Состояние стоков:<br />{status_msg}</p>'
'<p>'
'<br /><br />'
'--<br />'
'Дружески,<br />'
'{sender}'
'</p>'
'</body>'
'</html>'
)
return (
body_text.format(alert=alert_msg, status_msg=status_msg, sender=sender_name),
body_html.format(alert=alert_msg, status_msg=status_msg.replace('\r\n', '<br />'), sender=sender_name)
)


def send_mail(sender, recipient, subject, body, smtp_user, smtp_password,
smtp_gateway='email-smtp.eu-west-1.amazonaws.com', smtp_port=587):
"""Sends mail via SMTP server.
:param sender: tuple of two strings, ('Sender Name', '[email protected]')
:param recipient: str, recipient email, e.g. '[email protected]'
:param subject: str, email subject
:param body: tuple of two strings, (body_text, body_html)
:param smtp_gateway: str, smtp server endpoint
:param smtp_port: int, smtp server port
:param smtp_user: str, login of smtp user
:param smtp_password: str, password of smtp user
"""
body_text, body_html = body
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = email.utils.formataddr(sender)
msg['To'] = recipient
msg.attach(MIMEText(body_text, 'plain'))
msg.attach(MIMEText(body_html, 'html'))
server = smtplib.SMTP(smtp_gateway, smtp_port)
server.ehlo()
server.starttls()
server.ehlo()
server.login(smtp_user, smtp_password)
server.sendmail(sender[1], recipient, msg.as_string())
server.close()


if __name__ == '__main__':
rows = db_execute(
conf.db_name,
'SELECT account, quota, created_at FROM quota_check GROUP BY account HAVING MAX(created_at)'
)
info_items = list()
alert = False
for row in rows:
if row[0] not in conf.accounts:
continue # unknown account code
info_items.append({
'code': row[0],
'quota': row[1],
'datetime': time.strftime('%Y-%m-%d %H:%M:%S (%Z)', time.localtime(row[2])),
'alert': row[1] <= conf.accounts[row[0]].get('alert', 0),
})
alert = info_items[-1]['alert'] or alert
send_mail(conf.sender, conf.recipient_info, 'Состояние стоков в Сирене',
prepare_body(info_items, conf.sender[0]), conf.smtp_user, conf.smtp_password)
if alert:
send_mail(conf.sender, conf.recipient_alert, 'Срочно пополните сток в Сирене',
prepare_body(info_items, conf.sender[0]), conf.smtp_user, conf.smtp_password)
35 changes: 35 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
import argparse
from utils import db_execute
from conf import db_name


def setup_store(db_name):
db_execute(
db_name,
'''CREATE TABLE IF NOT EXISTS quota_check (
id INTEGER PRIMARY KEY,
account VARCHAR,
quota INTEGER,
created_at integer(4) not null default (strftime('%s','now'))
)'''
)


def drop_store(db_name):
db_execute(db_name, 'DROP TABLE IF EXISTS quota_check')


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('command', type=str, choices=['setup', 'drop'],
help='what to do with store of quota checks')
return parser.parse_args()


if __name__ == '__main__':
args = parse_args()
if args.command == 'setup':
setup_store(db_name)
elif args.command == 'drop':
drop_store(db_name)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.19.1
pytest==3.6.2
2 changes: 2 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .test_checker import TestChecker
12 changes: 12 additions & 0 deletions tests/test_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
import pytest
from checker import extract_ticket_quota


class TestChecker:
@pytest.mark.parametrize('rs', [
'<quota>958</quota>',
'<quota>0958</quota>'
])
def test_extract_quota(self, rs):
assert extract_ticket_quota(rs) == 958
24 changes: 24 additions & 0 deletions tests/test_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
import sqlite3
import os
import pytest
from manage import setup_store


class TestDB():
db_name = 'db/test.db'

def test_init_db(self):
try:
os.remove(self.db_name)
except OSError:
pytest.fail('Unexpected error trying to delete {}'.format(self.db_name), pytrace=True)
conn = sqlite3.connect(self.db_name)
conn.close()
assert os.path.isfile(self.db_name)

def test_setup_store(self):
try:
setup_store(self.db_name)
except sqlite3.Error:
pytest.fail('Unexpected error trying to setup store', pytrace=True)
13 changes: 13 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
import sqlite3


def db_execute(db_name, query):
"""Executes SQL query in sqlite database."""
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
cursor.execute(query)
rows = cursor.fetchall()
conn.commit()
conn.close()
return rows

0 comments on commit 5c26fa6

Please sign in to comment.