Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fixes #1 Create model for projects
  • Loading branch information
EliuX committed Mar 10, 2020
commit 667d626cfdd60c5b6d0d1dc4de0b6f7eae8fed7c
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ __pycache__/
.vs
.trash

# Jetbrans IDEs
.idea

# virtual environments
.venv
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
- `pip install -r requirements/prod.txt`

## Example usage in Windows
- `set FLASK_APP=time_tracker_api`
- `export FLASK_APP=time_tracker_api` or `set FLASK_APP=time_tracker_api`
- `flask run`
- Open `http://127.0.0.1:5000/` in a browser
5 changes: 4 additions & 1 deletion requirements/prod.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Flask==1.1.1
flask-restplus==0.13.0
flake8==3.7.9
Werkzeug==0.16.1
Werkzeug==0.16.1
wrapt==1.11.2
zipp==3.1.0
gunicorn==20.0.4
8 changes: 8 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python3
"""
This file is needed by gunicorn to run
"""
from time_tracker_api import create_app

app = create_app()
print("BPM Projects API server was created")
19 changes: 19 additions & 0 deletions time_tracker_api/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class MissingResource(Exception):
"""
Errors related to missing resource in the system
"""
pass


class InvalidInput(Exception):
"""
Errors related to an invalid input coming from the user
"""
pass


class InvalidMatch(Exception):
"""
Errors related to an invalid match during a search
"""
pass
36 changes: 22 additions & 14 deletions time_tracker_api/projects/projects_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
from flask_restplus import Namespace
from flask_restplus import Resource
from flask_restplus import Namespace, Resource, abort, inputs
from .projects_model import project_dao
from time_tracker_api.errors import MissingResource

ns = Namespace('projects', description='Api for resource `Projects`')


@ns.route('/')
class Projects(Resource):
@ns.doc('list_projects')
def get(self):
"""List all projects"""
print("List all projects")
return {}, 200
return project_dao.get_all(), 200

@ns.doc('create_project')
def post(self):
"""Create a project"""
print("List all projects")
return {}, 201
return project_dao.create(ns.payload), 201

project_update_parser = ns.parser()
project_update_parser.add_argument('active',
type=inputs.boolean,
location='form',
required=True,
help='Is the project active?')


@ns.route('/<string:uid>')
Expand All @@ -26,26 +31,29 @@ class Project(Resource):
@ns.doc('get_project')
def get(self, uid):
"""Retrieve a project"""
print("Retrieve Project")
return {}
return project_dao.get(uid)

@ns.doc('delete_project')
@ns.response(204, 'Project deleted')
def delete(self, uid):
"""Deletes a project"""
print("Delete Project")
project_dao.delete(uid)
return None, 204

@ns.doc('put_project')
def put(self, uid):
"""Create or replace a project"""
print("Create or Replace Project")
return {}
return project_dao.update(uid, ns.payload)

@ns.doc('update_project_status')
@ns.param('uid', 'The project identifier')
@ns.response(204, 'State of the project successfully updated')
def post(self, uid):
"""Updates a project using form data"""
print("Update Project using form data")
return {}
try:
update_data = project_update_parser.parse_args()
return project_dao.update(uid, update_data), 200
except ValueError:
abort(code=400)
except MissingResource as e:
abort(message=str(e), code=404)
80 changes: 80 additions & 0 deletions time_tracker_api/projects/projects_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from time_tracker_api.errors \
import MissingResource, InvalidInput, InvalidMatch


class InMemoryProjectDAO(object):
def __init__(self):
self.counter = 0
self.projects = []

def get_all(self):
return self.projects

def get(self, uid):
for project in self.projects:
if project.get('uid') == uid:
return project
raise MissingResource("Project '%s' not found" % uid)

def create(self, project):
self.counter += 1
project['uid'] = str(self.counter)
self.projects.append(project)
return project

def update(self, uid, data):
project = self.get(uid)
if project:
project.update(data)
return project
else:
raise MissingResource("Project '%s' not found" % uid)

def delete(self, uid):
if uid:
project = self.get(uid)
self.projects.remove(project)

def flush(self):
self.projects.clear()

def search(self, search_criteria):
matching_projects = self.select_matching_projects(search_criteria)

if len(matching_projects) > 0:
return matching_projects
else:
raise InvalidMatch("No project matched the specified criteria")

def select_matching_projects(self, user_search_criteria):
search_criteria = {k: v for k, v
in user_search_criteria.items()
if v is not None}

def matches_search_string(search_str, project):
return search_str in project['comments'] or \
search_str in project['short_name']

if not search_criteria:
raise InvalidInput("No search criteria specified")

search_str = search_criteria.get('search_string')
if search_str:
matching_projects = [p for p
in self.projects
if matches_search_string(search_str, p)]
else:
matching_projects = self.projects

is_active = search_criteria.get('active')
if is_active is not None:
matching_projects = [p for p
in matching_projects
if p['active'] is is_active]

return matching_projects


# Instances
# TODO Create an strategy to create other types of DAO
project_dao = InMemoryProjectDAO()