Skip to content

Commit

Permalink
Merge branch 'master' into 75_fix_paths
Browse files Browse the repository at this point in the history
  • Loading branch information
keiranjprice101 committed Oct 9, 2019
2 parents 03c015b + 1cf7dd9 commit 620a248
Show file tree
Hide file tree
Showing 60 changed files with 472 additions and 391 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ ICAT API to interface with the Data Gateway


## Requirements
All requirements can be installed with `pip install -r requirements.txt`

The required python libraries:
- [SQLAlchemy](https://www.sqlalchemy.org/)
- [flask-restful](https://github.com/flask-restful/flask-restful/)
- [pymysql](https://pymysql.readthedocs.io/en/latest/)
- [requests](https://2.python-requests.org/en/master/)
- [pyyaml](https://pyyaml.org/wiki/PyYAMLDocumentation) (For the swagger generation)
- [pip-tools](https://github.com/jazzband/pip-tools) (For generating requirements.txt)

## Setup and running the API
The database connection needs to be set up first. This is set in config.json, an example config file called `config.json.example` is provided.
Expand Down Expand Up @@ -116,6 +119,7 @@ and updating them, in a form easily converted to JSON.




## Running Tests
To run the tests use `python -m unittest discover`


13 changes: 13 additions & 0 deletions common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,17 @@ def is_generate_swagger(self):
except:
sys.exit("Missing config value, generate_swagger")

def get_host(self):
try:
return self.config["host"]
except:
sys.exit("Missing config value, host")

def get_port(self):
try:
return self.config["port"]
except:
sys.exit("Missing config value, port")


config = Config()
31 changes: 24 additions & 7 deletions common/database_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from sqlalchemy import asc, desc
from sqlalchemy.orm import aliased

from common.exceptions import MissingRecordError, BadFilterError, BadRequestError
from common.exceptions import MissingRecordError, BadFilterError, BadRequestError, MultipleIncludeError
from common.models.db_models import INVESTIGATIONUSER, INVESTIGATION, INSTRUMENT, FACILITYCYCLE, \
INVESTIGATIONINSTRUMENT, FACILITY
from common.session_manager import session_manager
Expand Down Expand Up @@ -86,6 +86,7 @@ class CreateQuery(Query):
def __init__(self, table, row):
super().__init__(table)
self.row = row
self.inserted_row = None

def execute_query(self):
"""Determines if the row is a row object or dictionary then commits it to the table"""
Expand All @@ -100,7 +101,8 @@ def execute_query(self):
record.MOD_ID = "user" # These will need changing
self.session.add(record)
self.commit_changes()

self.session.refresh(record)
self.inserted_row = record

class UpdateQuery(Query):

Expand Down Expand Up @@ -219,7 +221,10 @@ def __init__(self, included_filters):
self.included_filters = included_filters["include"]

def apply_filter(self, query):
query.include_related_entities = True
if not query.include_related_entities:
query.include_related_entities = True
else:
raise MultipleIncludeError("Attempted multiple includes on a single query")


class QueryFilterFactory(object):
Expand Down Expand Up @@ -292,16 +297,28 @@ def insert_row_into_table(table, row):
create_query.execute_query()


def create_row_from_json(table, json):
def create_row_from_json(table, data):
"""
Given a json dictionary create a row in the table from it
:param table: the table for the row to be inserted into
:param json: the dictionary containing the values
:return: nothing atm
:param data: the dictionary containing the values
:return: The inserted row as a dictionary
"""
with CreateQuery(table, json) as create_query:
with CreateQuery(table, data) as create_query:
create_query.execute_query()
return create_query.inserted_row.to_dict()

def create_rows_from_json(table, data):
"""
Given a List containing dictionary representations of entities, or a dictionary representation of an entity, insert
the entities into the table and return the created entities
:param table: The table to insert the entities in
:param data: The entities to be inserted
:return: The inserted entities
"""
if type(data) is list:
return [create_row_from_json(table, entity) for entity in data]
return create_row_from_json(table, data)

def get_row_by_id(table, id):
"""
Expand Down
4 changes: 4 additions & 0 deletions common/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class BadFilterError(ApiError):
pass


class MultipleIncludeError(BadFilterError):
pass


class AuthenticationError(ApiError):
pass

Expand Down
8 changes: 6 additions & 2 deletions common/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from common.database_helpers import QueryFilterFactory
from common.exceptions import MissingRecordError, BadFilterError, AuthenticationError, BadRequestError, \
MissingCredentialsError
MissingCredentialsError, MultipleIncludeError
from common.models.db_models import SESSION
from common.session_manager import session_manager

Expand Down Expand Up @@ -63,6 +63,9 @@ def wrapper_gets_records(*args, **kwargs):
except MissingRecordError as e:
log.exception(e)
return "No such record in table", 404
except MultipleIncludeError as e:
log.exception(e)
return "Bad request, only one include filter may be given per request", 400
except BadFilterError as e:
log.exception(e)
return "Invalid filter requested", 400
Expand All @@ -78,7 +81,6 @@ def wrapper_gets_records(*args, **kwargs):
except BadRequestError as e:
log.exception(e)
return "Bad request", 400

return wrapper_gets_records


Expand Down Expand Up @@ -109,6 +111,8 @@ def is_valid_json(string):
json_object = json.loads(string)
except ValueError:
return False
except TypeError:
return False
return True


Expand Down
4 changes: 3 additions & 1 deletion config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"DB_URL": "mysql+pymysql://root:rootpw@localhost:13306/icatdb",
"log_level": "WARN",
"debug_mode": false,
"generate_swagger": false
"generate_swagger": false,
"host": "127.0.0.1",
"port": "5000"
}


Expand Down
5 changes: 5 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
flask_restful == 0.3.7
sqlalchemy == 1.3.8
pymysql == 0.9.3
pyyaml == 5.1.2
flask-cors == 3.0.8
20 changes: 20 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile
#
aniso8601==8.0.0 # via flask-restful
click==7.0 # via flask
flask-cors==3.0.8
flask==1.1.1 # via flask-cors, flask-restful
flask_restful==0.3.7
itsdangerous==1.1.0 # via flask
jinja2==2.10.1 # via flask
markupsafe==1.1.1 # via jinja2
pymysql==0.9.3
pytz==2019.2 # via flask-restful
pyyaml==5.1.2
six==1.12.0 # via flask-cors, flask-restful
sqlalchemy==1.3.8
werkzeug==0.16.0 # via flask
4 changes: 4 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import Flask
from flask_cors import CORS
from flask_restful import Api

from common.config import config
Expand Down Expand Up @@ -49,6 +50,7 @@
swagger_gen.write_swagger_spec()

app = Flask(__name__)
cors = CORS(app)
app.url_map.strict_slashes = False
api = Api(app)

Expand Down Expand Up @@ -215,4 +217,6 @@
"/instruments/<int:instrument_id>/facilitycycles/<int:cycle_id>/investigations/count")

if __name__ == "__main__":
app.run(host=config.get_host(), port=config.get_port())
app.run(debug=config.is_debug_mode())

5 changes: 2 additions & 3 deletions src/resources/entities/applications_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from src.swagger.swagger_generator import swagger_gen

Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(APPLICATION, request.json)
return get_row_by_id(APPLICATION, request.json["id"].to_dict()), 200
return create_rows_from_json(APPLICATION, request.json), 200

@requires_session_id
@queries_records
Expand Down
6 changes: 3 additions & 3 deletions src/resources/entities/datacollection_datafiles_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATACOLLECTIONDATAFILE
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,8 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATACOLLECTIONDATAFILE, request.json)
return get_row_by_id(DATACOLLECTIONDATAFILE, request.json["id"].to_dict()), 200
return create_rows_from_json(DATACOLLECTIONDATAFILE, request.json), 200


@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datacollection_datasets_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATACOLLECTIONDATASET
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATACOLLECTIONDATASET, request.json)
return get_row_by_id(DATACOLLECTIONDATASET, request.json["id"].to_dict()), 200
return create_rows_from_json(DATACOLLECTIONDATASET, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datacollection_parameters_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, patch_entities, create_rows_from_json
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATACOLLECTIONPARAMETER
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATACOLLECTIONPARAMETER, request.json)
return get_row_by_id(DATACOLLECTIONPARAMETER, request.json["id"].to_dict()), 200
return create_rows_from_json(DATACOLLECTIONPARAMETER, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datacollections_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATACOLLECTION
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATACOLLECTION, request.json)
return get_row_by_id(DATACOLLECTION, request.json["id"].to_dict()), 200
return create_rows_from_json(DATACOLLECTION, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datafile_formats_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATAFILEFORMAT
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATAFILEFORMAT, request.json)
return get_row_by_id(DATAFILEFORMAT, request.json["id"].to_dict()), 200
return create_rows_from_json(DATAFILEFORMAT, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datafile_parameters_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATAFILEPARAMETER
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATAFILEPARAMETER, request.json)
return get_row_by_id(DATAFILEPARAMETER, request.json["id"].to_dict()), 200
return create_rows_from_json(DATAFILEPARAMETER, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/datafiles_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATAFILE
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATAFILE, request.json)
return "", 200
return create_rows_from_json(DATAFILE, request.json), 200

@requires_session_id
@queries_records
Expand Down
5 changes: 2 additions & 3 deletions src/resources/entities/dataset_type_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask_restful import Resource

from common.database_helpers import get_row_by_id, delete_row_by_id, update_row_from_id, get_rows_by_filter, \
get_filtered_row_count, get_first_filtered_row, create_row_from_json, patch_entities
get_filtered_row_count, get_first_filtered_row, create_rows_from_json, patch_entities
from common.helpers import requires_session_id, queries_records, get_filters_from_query_string
from common.models.db_models import DATASETTYPE
from src.swagger.swagger_generator import swagger_gen
Expand All @@ -18,8 +18,7 @@ def get(self):
@requires_session_id
@queries_records
def post(self):
create_row_from_json(DATASETTYPE, request.json)
return get_row_by_id(DATASETTYPE, request.json["id"].to_dict()), 200
return create_rows_from_json(DATASETTYPE, request.json), 200

@requires_session_id
@queries_records
Expand Down
Loading

0 comments on commit 620a248

Please sign in to comment.