
рд╣рдорд╛рд░реА рдЯреАрдо рдХрдИ рджреЗрд╢реЛрдВ, рд╢рд╣рд░реЛрдВ рдФрд░ рдХреНрд╖реЗрддреНрд░реЛрдВ рдХреЗ рд▓рд┐рдП рд╡реИрд╢реНрд╡рд┐рдХ рдбреЗрдЯрд╛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕реВрдЪрдирд╛ рд╕реЗрд╡рд╛ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░ рд░рд╣реА рд╣реИ ред рдЗрд╕ рд╡рд░реНрд╖ рдХреЗ рдлрд░рд╡рд░реА рдХреЗ рдЕрдВрдд рддрдХ, рджреБрдирд┐рдпрд╛ рднрд░ рдореЗрдВ рдХреЛрд░реЛрдирд╛рд╡рд╛рдпрд░рд╕ рдХреЗ рддреЗрдЬреА рд╕реЗ рдкреНрд░рд╕рд╛рд░ рдиреЗ рд╣рдореЗрдВ рдЕрдкрдиреЗ рдЖрд╡реЗрджрди рдореЗрдВ рд╕реНрдерд┐рддрд┐ рдХреА рдирд┐рдЧрд░рд╛рдиреА рдХреЗ рд▓рд┐рдП рдЕрддрд┐рд░рд┐рдХреНрдд рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░реЗрд░рд┐рдд рдХрд┐рдпрд╛ред рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдореЗрдВ рдбреЗрдЯрд╛ рдХреА рдХрд▓реНрдкрдирд╛ рдХрд░рдиреЗ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЗрд╕ рдХрд╛рд░реНрдп рдХрд╛ рдореБрдЦреНрдп рдШрдЯрдХ рд▓реЛрдХрдкреНрд░рд┐рдп рдлреНрд▓рд╛рд╕реНрдХ рд╡реЗрдм рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд╛рдпрдерди рдореЗрдВ рд▓рд┐рдЦрд╛ рдЧрдпрд╛ рдПрдХ рдорд╛рдЗрдХреНрд░реЛрд╕реЗрд╡рд╛ рдерд╛ред
рд╕реЗрд╡рд╛ рдирд┐рдпрдорд┐рдд рд░реВрдк рд╕реЗ рд╡рд┐рднрд┐рдиреНрди рд╕реНрд░реЛрддреЛрдВ рд╕реЗ рдбреЗрдЯрд╛ рдЕрдкрдбреЗрдЯ рдХрд░рддреА рд╣реИ, рдФрд░ рдЕрдиреБрд░реЛрдз рдкрд░, рд╡реЗрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдореЗрдВ рд╡рд┐рдЬрд╝реБрдЕрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рджрд╛рди рдХрд░рддреА рд╣реИред рдореБрдЦреНрдп рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рджреЗрд╢реЛрдВ рдФрд░ рдХреНрд╖реЗрддреНрд░реЛрдВ рдореЗрдВ рд╡рд╛рдпрд░рд╕ рдХреЗ рдкреНрд░рд╕рд╛рд░ рдкрд░ рд╡рд┐рдХрд┐рдкреАрдбрд┐рдпрд╛ рдкреГрд╖реНрда рд╣реИрдВред рдЗрди рдкреГрд╖реНрдареЛрдВ рдкрд░ рд╕рдВрдХреЗрддрдХ рдХреЗ рд╕рд╛рде рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЛ рдЬрд▓реНрджреА рд╕реЗ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рд╕рдВрдХреНрд░рдордг рдХреЗ рдкреНрд░рд╕рд╛рд░ рдХреА рдирд┐рдЧрд░рд╛рдиреА рдХреЗ рд▓рд┐рдП рд╕реЗрд╡рд╛ рдХреЗ рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рдХреЗ рд░реВрдк рдореЗрдВ рдЙрддреНрдХреГрд╖реНрдЯ рд╣реИрдВред
рд▓реЗрдЦ рдореЗрдВ, рдореИрдВ рдЧреНрд░рд╛рд╣рдХ рдХреЗ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдкреАрдЖрдИ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдФрд░ рдЕрджреНрдпрддрди рдХрд░рдиреЗ рд╕реЗ, рд╕реЗрд╡рд╛ рдХреЗ рдореБрдЦреНрдп рдШрдЯрдХреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реВрдВрдЧрд╛ред рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЛрдб github рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдЙрдкрд▓рдмреНрдз рд╣реИ ред
, Python Flask . , , . , COVID-19.
Flask
Python , ORM Python SQLAlchemy. Routitude PostgreSQL.
:
pip install requirements.txt
URI :
export COVID19API_DB_URI=< URI, : postgresql://localhost/covid19api>
. :
/api API
covid.py API c COVID-19
/datasources
/test
test_covid.py COVID-19
covid_wiki.py COVID-19
utils.py
/mirgations SQLAlchemy
... Flask-Migrate (Alembic)
/test
test_app.py (API)
app.py HTTP
appvars.py
config.py
manage.py CLI
models.py ORM
requirements.txt
, :
. github , , .
. :
:
html read_html Pandas. . . , , . , , . , . Pandas DataFrame, , html .
datasources/utils.py
import pandas as pd
def get_wiki_table_df(page_url, match_string):
response = requests.get(page_url)
tables = pd.read_html(response.content)
df = None
for table in tables:
df = table
if match_string in str(df):
break
return df
. . , COVID-19 .
datasources/covid_wiki.py
def get_report_countries():
url = (
'https://en.wikipedia.org/wiki/'
'2019%E2%80%9320_coronavirus_pandemic_by_country_and_territory'
)
df = utils.get_wiki_table_df(url, 'Locations[b]')
df = pd.DataFrame(
df.values[:, 1:5],
columns=['country', 'confirmed', 'deaths', 'recovered']
)
df = df[~df['country'].isna()]
df['country'] = df['country'].apply(lambda x: utils.clean_territory_name(x))
df.drop(df[df['country'].str.len() > 40].index, inplace=True)
df = utils.wiki_table_df_numeric_column_clean(df, [
'confirmed', 'deaths', 'recovered'
])
df['state'] = None
check_report(df)
return df
html , , , .
datasources/test/test_covid.py
from unittest import TestCase
from datasources import covid_wiki
class TestCovid(TestCase):
def test_get_wiki_report(self):
report = covid_wiki.get_report_countries()
self.assertTrue('Russia' in list(report['country']))
self.assertTrue(report.shape[0] > 0)
:
nosetests datasources
SQLAlchemy ORM Flask
, , . Python , , , . - (Object-Relational Mapping, ORM). Python ORM SQLAlchemy. , , , SQLAchemy . Alembic. , Flask , Flask-Migrate. appvars.py manage.py.
ORM Model SQLAlchemy. SQLAchemy . , . ORM .
models.py
class CovidWiki(db.Model):
__tablename__ = 'covid_wiki'
territory_id = Column(
db.VARCHAR(length=256), nullable=False, primary_key=True
)
update_time = Column(db.TIMESTAMP(), nullable=False)
country = Column(db.VARCHAR(length=128), nullable=False)
state = Column(db.VARCHAR(length=128), nullable=True)
confirmed = Column(db.INTEGER(), nullable=True)
deaths = Column(db.INTEGER(), nullable=True)
recovered = Column(db.INTEGER(), nullable=True)
Index('ix_covid_wiki_country', CovidWiki.country)
Index('ix_covid_wiki_state', CovidWiki.state)
, , Alembic:
python manage.py db init
migrations . ORM :
python manage.py db migrate -m covid_wiki
, , , , , , . migrations/versions. , , python, . 2 тАФ upgrate downgrade. ORM , , .
, . :
python manage.py db upgrade
.
COVID-19
ORM , pandas DataFrame , , , . , . , , SQLAlchemy.
models.py
def update_data_by_dataframe(self, df):
report = df.to_dict(orient='records')
report_last = self.get_wiki_last_report()
for value in report:
territory_id = self.get_id(value['country'], value['state'])
value['territory_id'] = territory_id
changed = (
(len(report_last) == 0) or
(territory_id not in report_last) or
(utils.get_covid_values_sum(value) !=
utils.get_covid_values_sum(report_last[territory_id]))
)
if not changed:
continue
logging.info(f"Updating data for territory: {territory_id}")
data = dict(value)
for name in utils.STAT_NAMES:
value = data[name]
if np.isnan(value):
data[name] = None
continue
data[name] = int(value)
data['update_time'] = datetime.datetime.now()
report = CovidWiki(**data)
db.session.merge(report)
db.session.commit()
. .
models.py
def update_data(self):
logging.info('Updating countries data')
self.update_data_by_dataframe(covid_wiki.get_report_countries())
logging.info('Updating Russian states data')
self.update_data_by_dataframe(covid_wiki.get_report_ru())
logging.info('Updating USA states data')
self.update_data_by_dataframe(covid_wiki.get_report_us())
Flask Flask-Script . , , manager.command . .
manage.py
@manager.command
def update_covid_data():
CovidWiki().update_data()
, , :
python manage.py update_covid_data
- , cron.
API COVID-19 Flask
. Flask . , SQLAlchemy . , COVID-19:
api/covid.py
def get_covid_countries_report():
data = db.session.query(CovidWiki).filter(CovidWiki.state.is_(None)).all()
return [v.to_dict() for v in data]
def get_covid_states_report_by_country(country):
data = db.session.query(CovidWiki).filter(and_(
CovidWiki.state.isnot(None),
func.lower(CovidWiki.country) == country.lower(),
)).all()
return [v.to_dict() for v in data]
def get_covid_total_stats():
def to_dict(v):
return {'confirmed': v[0], 'deaths': v[1], 'recovered': v[2]}
curr = db.session.query(
func.sum(CovidWiki.confirmed),
func.sum(CovidWiki.deaths),
func.sum(CovidWiki.recovered),
func.max(CovidWiki.update_time)
).filter(CovidWiki.state.is_(None)).one()
return {
'data': to_dict(curr),
'last_update_time': utils.datetime2string(curr[3], time=True)
}
API. , , .
app.py
@app.route('/covid/countries')
def get_covid_countries_report():
report = covid_api.get_covid_countries_report()
check_data(report)
return jsonify(report)
@app.route('/covid/states/<string:country>')
def get_covid_states_report_by_country(country):
report = covid_api.get_covid_states_report_by_country(country)
check_data(report)
return jsonify(report)
@app.route('/covid/total')
def get_covid_total_stats():
report = covid_api.get_covid_total_stats()
check_data(report)
return jsonify(report)
Flask .
python app.py
, , , curl.
curl http://localhost:5000/covid/total
. test, :
nosetests
-. Python gunicorn uWSGI.
, . API , . , .
рдкрд░ Routitude, рд╣рдо рдирдХреНрд╢рд╛ рдФрд░ рдбреИрд╢рдмреЛрд░реНрдб рдкрд░ COVID -19 рд╕рдВрдХреНрд░рдордг рдХреА рд╡рд░реНрддрдорд╛рди рджрд░реЛрдВ рдХреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЗрд╕ рд╕реЗрд╡рд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред рд╕рднреА рд╕реНрд░реЛрдд рд╕рд╛рдордЧреНрд░реА рдЧрд┐рддреБрдм рднрдВрдбрд╛рд░ рдореЗрдВ рдЙрдкрд▓рдмреНрдз рд╣реИрдВ ред рдХрд┐рд╕реА рднреА рд╕реБрдзрд╛рд░, рдмрдЧ рдлрд┐рдХреНрд╕, рдирдИ рд╕реБрд╡рд┐рдзрд╛рдУрдВ рдФрд░ рдбреЗрдЯрд╛ рдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИред рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рд╕реБрдзрд╛рд░ рдХреЗ рд▓рд┐рдП рд▓реЗрдЦ рдФрд░ рд╕реБрдЭрд╛рд╡реЛрдВ рдкрд░ рдХрд┐рд╕реА рднреА рдЯрд┐рдкреНрдкрдгреА рд╕реЗ рдореБрдЭреЗ рдЦреБрд╢реА рд╣реЛрдЧреАред