Skip to content

Commit

Permalink
Set up CSV Export Logic
Browse files Browse the repository at this point in the history
  • Loading branch information
maxachis committed Dec 9, 2024
1 parent 3a2216f commit 9f91477
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

@dataclass
class GetRelatedResourcesParameters:
db_client: DatabaseClient
dto: GetByIDBaseDTO
db_client_method: callable
primary_relation: Relations
related_relation: Relations
linking_column: str
metadata_count_name: str
db_client: DatabaseClient = DatabaseClient()
resource_name: str = "resource"


Expand Down
5 changes: 5 additions & 0 deletions middleware/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class AccessTypeEnum(Enum):
NO_AUTH = auto()


class OutputFormatEnum(Enum):

Check warning on line 45 in middleware/enums.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/enums.py#L45 <101>

Missing docstring in public class
Raw output
./middleware/enums.py:45:1: D101 Missing docstring in public class
JSON = "json"
CSV = "csv"


class Relations(Enum):
"""
A list of valid relations for the database
Expand Down
7 changes: 3 additions & 4 deletions middleware/location_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def get_location_id(

# In the case of a nonexistent locality, this can be added,
# provided the rest of the location is valid
county_id = _get_county_id(db_client, location_info_dict)

county_id = _get_county_id(location_info_dict)
# If this exists, locality does not yet exist in database and should be added. Add and return location id
db_client.create_locality(
column_value_mappings={
Expand All @@ -47,13 +46,13 @@ def _raise_if_not_locality(location_info, location_info_dict):
raise InvalidLocationError(f"{location_info_dict} is not a valid location")


def _get_county_id(db_client, location_info_dict) -> int:
def _get_county_id(location_info_dict: dict) -> int:
county_dict = {
"county_fips": location_info_dict["county_fips"],
"state_iso": location_info_dict["state_iso"],
"type": LocationType.COUNTY,
}
results = db_client._select_from_relation(
results = DatabaseClient()._select_from_relation(
relation_name=Relations.LOCATIONS_EXPANDED.value,
columns=["county_id"],
where_mappings=WhereMapping.from_dict(county_dict),
Expand Down
10 changes: 1 addition & 9 deletions middleware/primary_resource_logic/agencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def get_agencies(
"""
return get_many(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
access_info=access_info,
entry_name="agencies",
relation=Relations.AGENCIES_EXPANDED.value,
Expand All @@ -69,7 +68,6 @@ def get_agency_by_id(
) -> Response:
return get_by_id(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
access_info=access_info,
entry_name="agency",
relation=Relations.AGENCIES_EXPANDED.value,
Expand Down Expand Up @@ -151,11 +149,7 @@ def create_agency(
)

return post_entry(
middleware_parameters=MiddlewareParameters(
entry_name="agency",
relation=Relations.AGENCIES.value,
db_client_method=DatabaseClient.create_agency,
),
middleware_parameters=AGENCY_POST_MIDDLEWARE_PARAMETERS,
entry=entry_data,
pre_insertion_function_with_parameters=pre_insertion_function,
check_for_permission=False,
Expand Down Expand Up @@ -209,7 +203,6 @@ def update_agency(

return put_entry(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
access_info=access_info,
entry_name="agency",
relation=Relations.AGENCIES.value,
Expand All @@ -226,7 +219,6 @@ def delete_agency(
) -> Response:
return delete_entry(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
access_info=access_info,
entry_name="agency",
relation=Relations.AGENCIES.value,
Expand Down
14 changes: 6 additions & 8 deletions middleware/primary_resource_logic/batch_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from io import BytesIO

from marshmallow import Schema, ValidationError
from werkzeug.datastructures import FileStorage

from database_client.database_client import DatabaseClient
from middleware.dynamic_request_logic.supporting_classes import (
Expand All @@ -25,14 +26,14 @@
from middleware.schema_and_dto_logic.dynamic_logic.dynamic_schema_request_content_population import (
setup_dto_class,
)
from middleware.schema_and_dto_logic.primary_resource_dtos.agencies_dtos import (
AgenciesPostDTO,
)

from middleware.schema_and_dto_logic.primary_resource_dtos.batch_dtos import (
BatchRequestDTO,
)
from csv import DictReader

from middleware.util import bytes_to_text_iter, read_from_csv

Check warning on line 35 in middleware/primary_resource_logic/batch_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/batch_logic.py#L35 <401>

'middleware.util.bytes_to_text_iter' imported but unused
Raw output
./middleware/primary_resource_logic/batch_logic.py:35:1: F401 'middleware.util.bytes_to_text_iter' imported but unused


def replace_empty_strings_with_none(row: dict):
for key, value in row.items():
Expand All @@ -48,12 +49,9 @@ def _get_raw_rows_from_csv(
return raw_rows


def _get_raw_rows(file: BytesIO):
def _get_raw_rows(file: FileStorage):
try:
text_file = (line.decode("utf-8") for line in file)
reader = DictReader(text_file)
rows = list(reader)
return rows
return read_from_csv(file)
except Exception as e:
FlaskResponseManager.abort(
code=HTTPStatus.BAD_REQUEST, message=f"Error reading csv file: {e}"
Expand Down
140 changes: 104 additions & 36 deletions middleware/primary_resource_logic/search_logic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from csv import DictWriter

Check warning on line 1 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L1 <100>

Missing docstring in public module
Raw output
./middleware/primary_resource_logic/search_logic.py:1:1: D100 Missing docstring in public module
from http import HTTPStatus
from io import BytesIO, StringIO
from typing import Optional

from flask import Response, make_response
from flask import Response, make_response, send_file
from pydantic import BaseModel

from database_client.database_client import DatabaseClient
from database_client.db_client_dataclasses import WhereMapping
Expand All @@ -12,12 +15,13 @@
MiddlewareParameters,
IDInfo,
)
from middleware.enums import JurisdictionSimplified, Relations
from middleware.enums import JurisdictionSimplified, Relations, OutputFormatEnum
from middleware.flask_response_manager import FlaskResponseManager
from middleware.schema_and_dto_logic.primary_resource_schemas.search_schemas import (
SearchRequests,
)
from middleware.common_response_formatting import message_response
from middleware.util import get_datetime_now, write_to_csv, find_root_directory

Check warning on line 24 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L24 <401>

'middleware.util.write_to_csv' imported but unused
Raw output
./middleware/primary_resource_logic/search_logic.py:24:1: F401 'middleware.util.write_to_csv' imported but unused

Check warning on line 24 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L24 <401>

'middleware.util.find_root_directory' imported but unused
Raw output
./middleware/primary_resource_logic/search_logic.py:24:1: F401 'middleware.util.find_root_directory' imported but unused
from utilities.enums import RecordCategories


Expand Down Expand Up @@ -68,57 +72,99 @@ def format_search_results(search_results: list[dict]) -> dict:

response = {"count": 0, "data": {}}

data = response["data"]
# Create sub-dictionary for each jurisdiction
for jurisdiction in [j.value for j in JurisdictionSimplified]:
response["data"][jurisdiction] = {"count": 0, "results": []}
data[jurisdiction] = {"count": 0, "results": []}

for result in search_results:
jurisdiction_str = result.get("jurisdiction_type")
jurisdiction = get_jurisdiction_type_enum(jurisdiction_str)
response["data"][jurisdiction.value]["count"] += 1
response["data"][jurisdiction.value]["results"].append(result)
data[jurisdiction.value]["count"] += 1
data[jurisdiction.value]["results"].append(result)
response["count"] += 1

return response


def format_as_csv(ld: list[dict]) -> BytesIO:

Check warning on line 90 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L90 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:90:1: D103 Missing docstring in public function
string_output = StringIO()
writer = DictWriter(string_output, fieldnames=list(ld[0].keys()))
writer.writeheader()
writer.writerows(ld)
string_output.seek(0)
bytes_output = string_output.getvalue().encode("utf-8")
return BytesIO(bytes_output)


def search_wrapper(
db_client: DatabaseClient,
access_info: AccessInfoPrimary,
dto: SearchRequests,
) -> Response:
create_search_record(access_info, db_client, dto)
explicit_record_categories = get_explicit_record_categories(dto.record_categories)
search_results = db_client.search_with_location_and_record_type(
state=dto.state,
# Pass modified record categories, which breaks down ALL into individual categories
record_categories=explicit_record_categories,
county=dto.county,
locality=dto.locality,
)
return send_search_results(
search_results=search_results,
output_format=dto.output_format,
)


def create_search_record(access_info, db_client, dto):

Check warning on line 120 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L120 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:120:1: D103 Missing docstring in public function
location_id = try_getting_location_id_and_raise_error_if_not_found(
db_client=db_client,
dto=dto,
)
explicit_record_categories = get_explicit_record_categories(dto.record_categories)
db_client.create_search_record(
user_id=access_info.get_user_id(),
location_id=location_id,
# Pass originally provided record categories
record_categories=dto.record_categories,
)
search_results = db_client.search_with_location_and_record_type(
state=dto.state,
# Pass modified record categories, which breaks down ALL into individual categories
record_categories=explicit_record_categories,
county=dto.county,
locality=dto.locality,
)


def send_search_results(search_results: list[dict], output_format: OutputFormatEnum):

Check warning on line 133 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L133 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:133:1: D103 Missing docstring in public function
if output_format == OutputFormatEnum.JSON:
return send_as_json(search_results)
elif output_format == OutputFormatEnum.CSV:
return send_as_csv(search_results)
else:
FlaskResponseManager.abort(
message="Invalid output format.",
code=HTTPStatus.BAD_REQUEST,
)


def send_as_json(search_results):

Check warning on line 145 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L145 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:145:1: D103 Missing docstring in public function
formatted_search_results = format_search_results(search_results)
return make_response(formatted_search_results, HTTPStatus.OK)


def send_as_csv(search_results):

Check warning on line 150 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L150 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:150:1: D103 Missing docstring in public function
filename = f"search_results-{get_datetime_now()}.csv"
csv_stream = format_as_csv(ld=search_results)
return send_file(
csv_stream, download_name=filename, mimetype="text/csv", as_attachment=True
)


def get_explicit_record_categories(
record_categories=list[RecordCategories],
) -> list[RecordCategories]:
if record_categories == [RecordCategories.ALL]:
if RecordCategories.ALL in record_categories:
if len(record_categories) > 1:
FlaskResponseManager.abort(
message="ALL cannot be provided with other record categories.",
code=HTTPStatus.BAD_REQUEST,
)
return [rc for rc in RecordCategories if rc != RecordCategories.ALL]
elif len(record_categories) > 1 and RecordCategories.ALL in record_categories:
FlaskResponseManager.abort(
message="ALL cannot be provided with other record categories.",
code=HTTPStatus.BAD_REQUEST,
)
return record_categories


Expand Down Expand Up @@ -181,12 +227,25 @@ def make_response(self) -> Response:
)


def create_followed_search(
def get_link_id_and_raise_error_if_not_found(

Check warning on line 230 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L230 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:230:1: D103 Missing docstring in public function
db_client: DatabaseClient, access_info: AccessInfoPrimary, dto: SearchRequests
):
location_id = try_getting_location_id_and_raise_error_if_not_found(
db_client=db_client,
dto=dto,
)
return get_user_followed_search_link(
db_client=db_client,
access_info=access_info,
location_id=location_id,
)


def get_location_link_and_raise_error_if_not_found(

Check warning on line 244 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L244 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:244:1: D103 Missing docstring in public function
db_client: DatabaseClient,
access_info: AccessInfoPrimary,
dto: SearchRequests,
) -> Response:
# Get location id. If not found, not a valid location. Raise error
):
location_id = try_getting_location_id_and_raise_error_if_not_found(
db_client=db_client,
dto=dto,
Expand All @@ -196,22 +255,38 @@ def create_followed_search(
access_info=access_info,
location_id=location_id,
)
if link_id is not None:
return LocationLink(link_id=link_id, location_id=location_id)


class LocationLink(BaseModel):

Check warning on line 261 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L261 <101>

Missing docstring in public class
Raw output
./middleware/primary_resource_logic/search_logic.py:261:1: D101 Missing docstring in public class
link_id: Optional[int]
location_id: int


def create_followed_search(

Check warning on line 266 in middleware/primary_resource_logic/search_logic.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] middleware/primary_resource_logic/search_logic.py#L266 <103>

Missing docstring in public function
Raw output
./middleware/primary_resource_logic/search_logic.py:266:1: D103 Missing docstring in public function
db_client: DatabaseClient,
access_info: AccessInfoPrimary,
dto: SearchRequests,
) -> Response:
# Get location id. If not found, not a valid location. Raise error
location_link = get_location_link_and_raise_error_if_not_found(
db_client=db_client, access_info=access_info, dto=dto
)
if location_link.link_id is not None:
return message_response(
message="Location already followed.",
)

return post_entry(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
entry_name="followed search",
relation=Relations.LINK_USER_FOLLOWED_LOCATION.value,
db_client_method=DatabaseClient.create_followed_search,
access_info=access_info,
),
entry={
"user_id": access_info.get_user_id(),
"location_id": location_id,
"location_id": location_link.location_id,
},
check_for_permission=False,
post_logic_class=FollowedSearchPostLogic,
Expand All @@ -224,31 +299,24 @@ def delete_followed_search(
dto: SearchRequests,
) -> Response:
# Get location id. If not found, not a valid location. Raise error
location_id = try_getting_location_id_and_raise_error_if_not_found(
db_client=db_client,
dto=dto,
)
link_id = get_user_followed_search_link(
db_client=db_client,
access_info=access_info,
location_id=location_id,
location_link = get_location_link_and_raise_error_if_not_found(
db_client=db_client, access_info=access_info, dto=dto
)
# Check if search is followed. If not, end early .
if link_id is None:
if location_link.link_id is None:
return message_response(
message="Location not followed.",
)

return delete_entry(
middleware_parameters=MiddlewareParameters(
db_client=db_client,
entry_name="Followed search",
relation=Relations.LINK_USER_FOLLOWED_LOCATION.value,
db_client_method=DatabaseClient.delete_followed_search,
access_info=access_info,
),
id_info=IDInfo(
id_column_name="id",
id_column_value=link_id,
id_column_value=location_link.link_id,
),
)
Loading

0 comments on commit 9f91477

Please sign in to comment.