Skip to content

Commit

Permalink
refactor: remove database backend #469
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuadkitenge committed Nov 26, 2024
1 parent 1f0aba1 commit 5e5d6c4
Show file tree
Hide file tree
Showing 48 changed files with 398 additions and 2,853 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ per-file-ignores =
test/*: S101
util/icat_db_generator.py: S311
datagateway_api/wsgi.py:E402,F401
datagateway_api/src/datagateway_api/database/models.py: N815,A003
datagateway_api/src/datagateway_api/icat/models.py: N815,A003
datagateway_api/src/datagateway_api/icat/filters.py: C901
datagateway_api/src/search_api/models.py: B950
enable-extensions=G
2 changes: 0 additions & 2 deletions datagateway_api/config.yaml.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
---
datagateway_api:
extension: "/"
backend: "python_icat"
client_cache_size: 5
client_pool_init_size: 2
client_pool_max_size: 5
db_url: "mysql+pymysql://icatdbuser:icatdbuserpw@localhost:3306/icatdb"
icat_url: "https://localhost:8181"
icat_check_cert: false
use_reader_for_performance:
Expand Down
48 changes: 10 additions & 38 deletions datagateway_api/src/api_start_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,8 @@
from flask_swagger_ui import get_swaggerui_blueprint

from datagateway_api.src.common.config import Config

# Only attempt to create a DataGateway API backend if the datagateway_api object
# is present in the config. This ensures that the API does not error on startup
# due to an AttributeError exception being thrown if the object is missing.
if Config.config.datagateway_api:
from datagateway_api.src.datagateway_api.backends import create_backend
from datagateway_api.src.datagateway_api.database.helpers import db # noqa: I202
from datagateway_api.src.datagateway_api.icat.icat_client_pool import create_client_pool
from datagateway_api.src.datagateway_api.icat.python_icat import PythonICAT
from datagateway_api.src.resources.entities.entity_endpoint import (
get_count_endpoint,
get_endpoint,
Expand Down Expand Up @@ -112,21 +106,6 @@ def create_app_infrastructure(flask_app):
CORS(flask_app)
flask_app.url_map.strict_slashes = False
api = CustomErrorHandledApi(flask_app)

if Config.config.datagateway_api is not None:
try:
backend_type = flask_app.config["TEST_BACKEND"]
Config.config.datagateway_api.set_backend_type(backend_type)
except KeyError:
backend_type = Config.config.datagateway_api.backend

if backend_type == "db":
flask_app.config[
"SQLALCHEMY_DATABASE_URI"
] = Config.config.datagateway_api.db_url
flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(flask_app)

specs = []
if Config.config.datagateway_api is not None:
configure_datagateway_api_swaggerui_blueprint(flask_app)
Expand All @@ -148,25 +127,18 @@ def create_api_endpoints(flask_app, api, specs):
datagateway_api_spec = next(
(spec for spec in specs if spec.title == "DataGateway API"), None,
)
try:
backend_type = flask_app.config["TEST_BACKEND"]
Config.config.datagateway_api.set_backend_type(backend_type)
except KeyError:
backend_type = Config.config.datagateway_api.backend

backend = create_backend(backend_type)
python_icat = PythonICAT()

icat_client_pool = None
if backend_type == "python_icat":
# Create client pool
icat_client_pool = create_client_pool()
# Create client pool
icat_client_pool = create_client_pool()

datagateway_api_extension = Config.config.datagateway_api.extension
for entity_name in endpoints:
get_endpoint_resource = get_endpoint(
entity_name,
endpoints[entity_name],
backend,
python_icat,
client_pool=icat_client_pool,
)
api.add_resource(
Expand All @@ -179,7 +151,7 @@ def create_api_endpoints(flask_app, api, specs):
get_id_endpoint_resource = get_id_endpoint(
entity_name,
endpoints[entity_name],
backend,
python_icat,
client_pool=icat_client_pool,
)
api.add_resource(
Expand All @@ -192,7 +164,7 @@ def create_api_endpoints(flask_app, api, specs):
get_count_endpoint_resource = get_count_endpoint(
entity_name,
endpoints[entity_name],
backend,
python_icat,
client_pool=icat_client_pool,
)
api.add_resource(
Expand All @@ -205,7 +177,7 @@ def create_api_endpoints(flask_app, api, specs):
get_find_one_endpoint_resource = get_find_one_endpoint(
entity_name,
endpoints[entity_name],
backend,
python_icat,
client_pool=icat_client_pool,
)
api.add_resource(
Expand All @@ -217,7 +189,7 @@ def create_api_endpoints(flask_app, api, specs):

# Session endpoint
session_endpoint_resource = session_endpoints(
backend, client_pool=icat_client_pool,
python_icat, client_pool=icat_client_pool,
)
api.add_resource(
session_endpoint_resource,
Expand All @@ -227,7 +199,7 @@ def create_api_endpoints(flask_app, api, specs):
datagateway_api_spec.path(resource=session_endpoint_resource, api=api)

# Ping endpoint
ping_resource = ping_endpoint(backend, client_pool=icat_client_pool)
ping_resource = ping_endpoint(python_icat, client_pool=icat_client_pool)
api.add_resource(ping_resource, f"{datagateway_api_extension}/ping")
datagateway_api_spec.path(resource=ping_resource, api=api)

Expand Down
54 changes: 7 additions & 47 deletions datagateway_api/src/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,12 @@ class UseReaderForPerformance(BaseModel):
class DataGatewayAPI(BaseModel):
"""
Configuration model class that implements pydantic's BaseModel class to allow for
validation of the DataGatewayAPI config data using Python type annotations. It takes
the backend into account, meaning only the config options for the backend used are
required.
validation of the DataGatewayAPI config data using Python type annotations.
"""

backend: StrictStr
client_cache_size: Optional[StrictInt]
client_pool_init_size: Optional[StrictInt]
client_pool_max_size: Optional[StrictInt]
db_url: Optional[StrictStr]
extension: StrictStr
icat_check_cert: Optional[StrictBool]
icat_url: Optional[StrictStr]
Expand All @@ -68,23 +64,6 @@ class DataGatewayAPI(BaseModel):
def __getitem__(self, item):
return getattr(self, item)

@validator("db_url", always=True)
def require_db_config_value(cls, value, values): # noqa: B902, N805
"""
By default the `db_url` config field is optional so that it does not have to be
present in the config file if `backend` is set to `python_icat`. However, if the
`backend` is set to `db`, this validator esentially makes the `db_url` config
field mandatory. This means that an error is raised, at which point the
application exits, if a `db_url` config value is not present in the config file.
:param cls: :class:`DataGatewayAPI` pointer
:param value: The value of the given config field
:param values: The config field values loaded before the given config field
"""
if "backend" in values and values["backend"] == "db" and value is None:
raise TypeError("field required")
return value

@validator(
"client_cache_size",
"client_pool_init_size",
Expand All @@ -93,44 +72,25 @@ def require_db_config_value(cls, value, values): # noqa: B902, N805
"icat_url",
always=True,
)
def require_icat_config_value(cls, value, values): # noqa: B902, N805
def require_icat_config_value(cls, value): # noqa: B902, N805
"""
By default the above config fields that are passed to the `@validator` decorator
are optional so that they do not have to be present in the config file if
`backend` is set to `db`. However, if the `backend` is set to `python_icat`,
this validator esentially makes these config fields mandatory. This means that
an error is raised, at which point the application exits, if any of these config
values are not present in the config file.
Validates that the required config fields for the `python_icat`
are present and not None. If any of these config values are missing,
an error is raised, causing the application to exit.
:param cls: :class:`DataGatewayAPI` pointer
:param value: The value of the given config field
:param values: The config field values loaded before the given config field
"""

if "backend" in values and values["backend"] == "python_icat" and value is None:
raise TypeError("field required")
if value is None:
raise TypeError("Field required for `python_icat`.")
return value

def set_backend_type(self, backend_type):
"""
This setter is used as a way for automated tests to set the backend type. The
API can detect if the Flask app setup is from an automated test by checking the
app's config for a `TEST_BACKEND`. If this value exists (a KeyError will be
raised when the API is run normally, which will then grab the backend type from
`config.yaml`), it needs to be set using this function. This is required because
creating filters in the `QueryFilterFactory` is backend-specific so the backend
type must be fetched. This must be done using this module (rather than directly
importing and checking the Flask app's config) to avoid circular import issues.
"""
self.backend = backend_type

class Config:
"""
The behaviour of the BaseModel class can be controlled via this class.
"""

# Enables assignment validation on the BaseModel fields. Useful for when the
# backend type is changed using the set_backend_type function.
validate_assignment = True


Expand Down
3 changes: 1 addition & 2 deletions datagateway_api/src/common/date_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ def is_str_a_date(potential_date):
@staticmethod
def str_to_datetime_object(data):
"""
Convert a string to a `datetime.datetime` object. This is commonly used when
storing user input in ICAT (using the Python ICAT backend).
Convert a string to a `datetime.datetime` object.
Python 3.7+ has support for `datetime.fromisoformat()` which would be a more
elegant solution to this conversion operation since dates are converted into ISO
Expand Down
2 changes: 1 addition & 1 deletion datagateway_api/src/common/filter_order_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def clear_python_icat_order_filters(self):
def manage_icat_filters(self, filters, query):
"""
Utility function to call other functions in this class, used to manage filters
when using the Python ICAT backend. These steps are the same with the different
when using the Python ICAT. These steps are the same with the different
types of requests that utilise filters, therefore this function helps to reduce
code duplication
Expand Down
4 changes: 2 additions & 2 deletions datagateway_api/src/common/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class WhereFilter(QueryFilter):
precedence = 1

def __init__(self, field, value, operation):
# The field is set to None as a precaution but this should be set by the
# individual backend since backends deal with this data differently
# The field is set to None as a precaution but this should be set
# when initialising Python ICAT
self.field = None
self.value = value
self.operation = operation
Expand Down
4 changes: 2 additions & 2 deletions datagateway_api/src/common/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
FilterError,
MissingCredentialsError,
)
from datagateway_api.src.datagateway_api.database import models
from datagateway_api.src.datagateway_api.icat import models
from datagateway_api.src.resources.entities.entity_endpoint_dict import endpoints

log = logging.getLogger()
Expand Down Expand Up @@ -137,7 +137,7 @@ def get_entity_object_from_name(entity_name):
:param entity_name: Name of the entity to fetch a version from this model
:type entity_name: :class:`str`
:return: Object of the entity requested (e.g.
:class:`.datagateway_api.database.models.INVESTIGATIONINSTRUMENT`)
:class:`.datagateway_api.icat.models.INVESTIGATIONINSTRUMENT`)
:raises: KeyError: If an entity model cannot be found as a class in this model
"""
try:
Expand Down
Loading

0 comments on commit 5e5d6c4

Please sign in to comment.