Skip to content

Commit ba0ad4d

Browse files
authored
Merge pull request #24 from ioet/feature/Create-configuration-and-database-routing#23
Close #24 - Create configuration and database routing
2 parents 55b66ea + ad77389 commit ba0ad4d

File tree

14 files changed

+149
-49
lines changed

14 files changed

+149
-49
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ We are using Pytest](https://docs.pytest.org/en/latest/index.html) for tests. Th
6363
6464
To run the tests just execute:
6565
66-
```
66+
```bash
6767
python3 -m pytest -v
6868
```
6969

requirements/dev.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
# For development
66

77
# Tests
8-
pytest==4.1.1
8+
pytest==5.2.0
9+
10+
# Mocking
11+
pytest-mock==2.0.0
912

1013
# Coverage
1114
coverage==4.5.1

tests/conftest.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
21
import pytest
2+
from flask import Flask
3+
from flask.testing import FlaskClient
34

45
from time_tracker_api import create_app
56

7+
68
@pytest.fixture(scope='session')
7-
def app():
9+
def app() -> Flask:
810
"""An instance of the app for tests"""
911
return create_app()
1012

13+
1114
@pytest.fixture
12-
def client(app):
15+
def client(app: Flask) -> FlaskClient:
1316
"""A test client for the app."""
1417
with app.test_client() as c:
15-
return c
18+
return c

tests/projects/projects_namespace_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ def test_list_should_return_nothing(client):
88
assert 200 == response.status_code
99

1010
json_data = json.loads(response.data)
11-
assert [] == json_data
11+
assert [] == json_data

tests/time_entries/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from flask import json
2+
from flask.testing import FlaskClient
3+
from pytest_mock import MockFixture
4+
5+
6+
def test_list_should_return_empty_array(mocker: MockFixture, client: FlaskClient):
7+
from time_tracker_api.time_entries.time_entries_namespace import model
8+
"""Should return an empty array"""
9+
model_mock = mocker.patch.object(model, 'find_all', return_value=[])
10+
11+
response = client.get("/time-entries", follow_redirects=True)
12+
13+
assert 200 == response.status_code
14+
15+
json_data = json.loads(response.data)
16+
assert [] == json_data
17+
model_mock.assert_called_once()

time_tracker_api/__init__.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
1+
import os
2+
13
from flask import Flask
24

35

4-
def create_app():
6+
def create_app(config_path='time_tracker_api.config.DefaultConfig',
7+
config_data=None):
58
flask_app = Flask(__name__)
69

10+
init_app_config(flask_app, config_path, config_data)
711
init_app(flask_app)
812

913
return flask_app
1014

1115

16+
def init_app_config(app: Flask, config_path: str, config_data: dict = None):
17+
if config_path:
18+
app.config.from_object(config_path)
19+
else:
20+
# ensure the instance folder exists
21+
try:
22+
os.makedirs(app.instance_path)
23+
except OSError:
24+
pass
25+
26+
# Located in `/instance`
27+
app.config.from_pyfile('config.py', silent=True)
28+
29+
if config_data:
30+
app.config.update(config_data)
31+
32+
1233
def init_app(app: Flask):
34+
from .database import init_app as init_database
35+
init_database(app)
36+
1337
from .api import api
1438
api.init_app(app)

time_tracker_api/activities/activities_namespace.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
ns = Namespace('activities', description='API for activities')
55

66
# Activity Model
7-
activity = ns.model('Activity', {
7+
activity_input = ns.model('ActivityInput', {
88
'name': fields.String(
99
required=True,
1010
title='Name',
@@ -27,24 +27,24 @@
2727
}
2828
activity_response_fields.update(audit_fields)
2929

30-
activity_response = ns.inherit(
31-
'ActivityResponse',
32-
activity,
30+
activity = ns.inherit(
31+
'Activity',
32+
activity_input,
3333
activity_response_fields
3434
)
3535

3636

3737
@ns.route('')
3838
class Activities(Resource):
3939
@ns.doc('list_activities')
40-
@ns.marshal_list_with(activity_response, code=200)
40+
@ns.marshal_list_with(activity, code=200)
4141
def get(self):
4242
"""List all available activities"""
4343
return []
4444

4545
@ns.doc('create_activity')
46-
@ns.expect(activity)
47-
@ns.marshal_with(activity_response, code=201)
46+
@ns.expect(activity_input)
47+
@ns.marshal_with(activity, code=201)
4848
@ns.response(400, 'Invalid format of the attributes of the activity.')
4949
def post(self):
5050
"""Create a single activity"""
@@ -56,7 +56,7 @@ def post(self):
5656
@ns.param('id', 'The unique identifier of the activity')
5757
class Activity(Resource):
5858
@ns.doc('get_activity')
59-
@ns.marshal_with(activity_response)
59+
@ns.marshal_with(activity)
6060
def get(self, id):
6161
"""Retrieve all the data of a single activity"""
6262
return {}
@@ -69,8 +69,8 @@ def delete(self, id):
6969

7070
@ns.doc('put_activity')
7171
@ns.response(400, 'Invalid format of the attributes of the activity.')
72-
@ns.expect(activity)
73-
@ns.marshal_with(activity_response)
72+
@ns.expect(activity_input)
73+
@ns.marshal_with(activity)
7474
def put(self, id):
7575
"""Updates an activity"""
7676
return ns.payload

time_tracker_api/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Config:
2+
DEBUG = False
3+
4+
5+
class WhateverDevelopConfig(Config):
6+
DEBUG = True
7+
FLASK_DEBUG = True
8+
FLASK_ENV = "develop"
9+
DATABASE = "whatever"
10+
11+
12+
DefaultConfig = WhateverDevelopConfig

time_tracker_api/database.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
Agnostic database assets
3+
4+
Put here your utils and class independent of
5+
the database solution
6+
"""
7+
from flask import Flask
8+
9+
RepositoryModel = None
10+
11+
12+
def init_app(app: Flask) -> None:
13+
"""Make the app ready to use the database"""
14+
database_strategy_name = app.config['DATABASE']
15+
with app.app_context():
16+
module = globals()["use_%s" % database_strategy_name]()
17+
global RepositoryModel
18+
RepositoryModel = module.repository_model
19+
20+
21+
def create(model_name: str):
22+
"""Creates the repository instance for the chosen database"""
23+
return RepositoryModel(model_name)
24+
25+
26+
def use_whatever():
27+
from time_tracker_api import whatever_repository
28+
return whatever_repository

0 commit comments

Comments
 (0)