Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sdunesme committed Jan 28, 2022
1 parent 3156013 commit 7d9dd6f
Show file tree
Hide file tree
Showing 27 changed files with 1,118 additions and 2 deletions.
18 changes: 18 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Flask configuration
FLASK_ENV=development
FLASK_APP=woodcamrm
SECRET_KEY=dev

# Database configuration
DATABASE_SERVER=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=

# External hydrometric observations API terminaison
API_URL=https://hubeau.eaufrance.fr/api/v1/hydrometrie/observations_tr

# App configuration
DEFAULT_USER=admin
DEFAULT_PASSWORD=admin
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:alpine
COPY requirements.txt ./requirements.txt
RUN pip install -r requirements.txt
COPY . ./

CMD gunicorn -b 0.0.0.0:80 app.app:server
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include woodcamrm/schema.sql
graft woodcamrm/templates
global-exclude *.pyc
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
# woodcam-rd
WoodCam Records Downloader
# WoodCam RM
WoodCam Records Manager

## Quick start

# Create python virtualenv
python -m venv env --prompt woodcam-rm
source env/bin/activate

# Install dependencies
python -m pip install -r requirements.txt

# Initialize database
flask init-db

# Run application
flask run
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
python-dotenv==0.19.2
Flask==2.0.2
requests
flask_restful
psycopg2
pytest
pytest-dotenv
coverage
7 changes: 7 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool:pytest]
testpaths = tests

[coverage:run]
branch = True
source =
woodcamrm
16 changes: 16 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from setuptools import find_packages, setup

setup(
name='woodcam_rm',
version='1.0.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask',
'flask_restful',
'requests',
'psycopg2',
'python-dotenv'
],
)
56 changes: 56 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os

import pytest
from woodcamrm import create_app
from woodcamrm.db import get_db, init_db

with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
_data_sql = f.read().decode('utf8')


@pytest.fixture
def app():

app = create_app({
'TESTING': True,
'DATABASE_NAME': 'woodcamrmtesting',
})

with app.app_context():
init_db()
db = get_db()
cur = db.cursor()
cur.execute(_data_sql)
cur.close()
db.commit()

yield app


@pytest.fixture
def client(app):
return app.test_client()


@pytest.fixture
def runner(app):
return app.test_cli_runner()


class AuthActions(object):
def __init__(self, client):
self._client = client

def login(self, username='test', password='test'):
return self._client.post(
'/auth/login',
data={'username': username, 'password': password}
)

def logout(self):
return self._client.get('/auth/logout')


@pytest.fixture
def auth(client):
return AuthActions(client)
18 changes: 18 additions & 0 deletions tests/data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
INSERT INTO users (username, password, role)
VALUES
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f', 'viewer'),
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79', 'administrator');

INSERT INTO stations (common_name, api_name)
VALUES
('Ain-Chazey', 'V2942010'),
('Station Test', NULL);

INSERT INTO hydrodata (api_name, metric, date_begin_serie, date_end_serie, date_obs, observation)
VALUES
('V2942010', 'H', TIMESTAMP '2022-01-26 00:00', TIMESTAMP '2022-01-26 15:00', TIMESTAMP '2022-01-26 14:12', 920.0);

INSERT INTO records (station_id, date_begin_record, date_end_record, host, path)
VALUES
(1, TIMESTAMP '2022-01-26 15:00', TIMESTAMP '2022-01-26 15:59', '127.0.0.1', '/data/records/15to16.mkv'),
(1, TIMESTAMP '2022-01-26 16:00', TIMESTAMP '2022-01-26 16:59', '127.0.0.1', '/data/records/16to17.mkv');
57 changes: 57 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import pytest
from flask import g, session
from woodcamrm.db import get_db


# def test_register(client, app):
# assert client.get('/auth/register').status_code == 200
# response = client.post(
# '/auth/register', data={'username': 'a', 'password': 'a'}
# )
# assert 'http://localhost/auth/login' == response.headers['Location']

# with app.app_context():
# assert get_db().execute(
# "SELECT * FROM user WHERE username = 'a'",
# ).fetchone() is not None


# @pytest.mark.parametrize(('username', 'password', 'message'), (
# ('', '', b'Username is required.'),
# ('a', '', b'Password is required.'),
# ('test', 'test', b'already registered'),
# ))
# def test_register_validate_input(client, username, password, message):
# response = client.post(
# '/auth/register',
# data={'username': username, 'password': password}
# )
# assert message in response.data


def test_login(client, auth):
assert client.get('/auth/login').status_code == 200
response = auth.login()
assert response.headers['Location'] == 'http://localhost/'

with client:
client.get('/')
assert session['user_id'] == 2
assert g.user['username'] == 'test'


@pytest.mark.parametrize(('username', 'password', 'message'), (
('a', 'test', b'Incorrect username.'),
('test', 'a', b'Incorrect password.'),
))
def test_login_validate_input(auth, username, password, message):
response = auth.login(username, password)
assert message in response.data


def test_logout(client, auth):
auth.login()

with client:
auth.logout()
assert 'user_id' not in session
31 changes: 31 additions & 0 deletions tests/test_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import psycopg2

import pytest
from woodcamrm.db import get_db


# def test_get_close_db(app):
# with app.app_context():
# db = get_db()
# assert db is get_db()

# with pytest.raises(psycopg2.ProgrammingError) as e:
# cur = db.cursor()
# db.execute('SELECT 1;')
# cur.close()
# db.commit()

# assert 'closed' in str(e.value)


def test_init_db_command(runner, monkeypatch):
class Recorder(object):
called = False

def fake_init_db():
Recorder.called = True

monkeypatch.setattr('woodcamrm.db.init_db', fake_init_db)
result = runner.invoke(args=['init-db'])
assert 'Initialized' in result.output
assert Recorder.called
11 changes: 11 additions & 0 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from woodcamrm import create_app


def test_config():
assert not create_app().testing
assert create_app({'TESTING': True}).testing


def test_hello(client):
response = client.get('/hello')
assert response.data == b'Hello, World!'
60 changes: 60 additions & 0 deletions woodcamrm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from ensurepip import bootstrap
import os

from psycopg2.extras import RealDictCursor

from flask import Flask


def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY=os.environ.get("SECRET_KEY"),
DATABASE_SERVER=os.environ.get("DATABASE_SERVER"),
DATABASE_PORT=os.environ.get("DATABASE_PORT"),
DATABASE_NAME=os.environ.get("DATABASE_NAME"),
DATABASE_USER=os.environ.get("DATABASE_USER"),
DATABASE_PASSWORD=os.environ.get("DATABASE_PASSWORD"),
DEFAULT_USER=os.environ.get("DEFAULT_USER"),
DEFAULT_PASSWORD=os.environ.get("DEFAULT_PASSWORD"),
)

if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)

# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass

from . import db
db.init_app(app)

# List all stations for sidebar
@app.context_processor
def inject_pages():
database = db.get_db()
cur = database.cursor(cursor_factory=RealDictCursor)
cur.execute(f"SELECT id, common_name FROM stations;")
stations = cur.fetchall()
cur.close()

return dict(pages=stations)

from . import auth
app.register_blueprint(auth.bp)

from . import station
app.register_blueprint(station.bp)

from . import home
app.register_blueprint(home.bp)
app.add_url_rule('/', endpoint='index')

return app
54 changes: 54 additions & 0 deletions woodcamrm/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import threading
import requests
import urllib.parse
import time
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

uptime_data = {'seconds': 0}
hydrodata = {}
hydrodata_api = "https://hubeau.eaufrance.fr/api/"

def update_uptime():
while True:
time.sleep(1)
vals = {'seconds': uptime_data['seconds'] + 1}
uptime_data.update(vals)

uptime_thread = threading.Thread(name='update_uptime', target=update_uptime)
uptime_thread.setDaemon(True)
uptime_thread.start()

class uptime(Resource):
def get(self):
return uptime_data

def update_hydrodata():
while True:
time.sleep(30)
obs_url = urllib.parse.urljoin(hydrodata_api, "v1/hydrometrie/observations_tr")
rep = requests.get(obs_url, data={"code_entite":"V2942010"})
data = rep.json()['data'][0]
hydrodata.update(data)

hydrodata_thread = threading.Thread(name='update_hydrodata', target=update_hydrodata)
hydrodata_thread.setDaemon(True)
hydrodata_thread.start()

class hydro(Resource):
def get(self):
return hydrodata


@app.route("/")
def root_page():
return hydrodata

api.add_resource(uptime, '/api/uptime')
api.add_resource(hydro, '/api/hydro')

if __name__ == "__main__":
app.run()
Loading

0 comments on commit 7d9dd6f

Please sign in to comment.