Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
[Issue #12] Setup the opportunity v1 endpoint which will be backed by…
Browse files Browse the repository at this point in the history
… the index
  • Loading branch information
chouinar committed May 20, 2024
1 parent 6944695 commit fab0d18
Show file tree
Hide file tree
Showing 16 changed files with 777 additions and 12 deletions.
8 changes: 5 additions & 3 deletions api/src/api/opportunities_v0_1/opportunity_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@

@opportunity_blueprint.post("/opportunities/search")
@opportunity_blueprint.input(
opportunity_schemas.OpportunitySearchRequestSchema, arg_name="search_params", examples=examples
opportunity_schemas.OpportunitySearchRequestV01Schema,
arg_name="search_params",
examples=examples,
)
# many=True allows us to return a list of opportunity objects
@opportunity_blueprint.output(opportunity_schemas.OpportunitySchema(many=True))
@opportunity_blueprint.output(opportunity_schemas.OpportunityV01Schema(many=True))
@opportunity_blueprint.auth_required(api_key_auth)
@opportunity_blueprint.doc(description=SHARED_ALPHA_DESCRIPTION)
@flask_db.with_db_session()
Expand All @@ -90,7 +92,7 @@ def opportunity_search(db_session: db.Session, search_params: dict) -> response.


@opportunity_blueprint.get("/opportunities/<int:opportunity_id>")
@opportunity_blueprint.output(opportunity_schemas.OpportunitySchema)
@opportunity_blueprint.output(opportunity_schemas.OpportunityV01Schema)
@opportunity_blueprint.auth_required(api_key_auth)
@opportunity_blueprint.doc(description=SHARED_ALPHA_DESCRIPTION)
@flask_db.with_db_session()
Expand Down
16 changes: 8 additions & 8 deletions api/src/api/opportunities_v0_1/opportunity_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from src.pagination.pagination_schema import generate_pagination_schema


class OpportunitySummarySchema(Schema):
class OpportunitySummaryV01Schema(Schema):
summary_description = fields.String(
metadata={
"description": "The summary of the opportunity",
Expand Down Expand Up @@ -178,7 +178,7 @@ class OpportunitySummarySchema(Schema):
applicant_types = fields.List(fields.Enum(ApplicantType))


class OpportunityAssistanceListingSchema(Schema):
class OpportunityAssistanceListingV01Schema(Schema):
program_title = fields.String(
metadata={
"description": "The name of the program, see https://sam.gov/content/assistance-listings for more detail",
Expand All @@ -193,7 +193,7 @@ class OpportunityAssistanceListingSchema(Schema):
)


class OpportunitySchema(Schema):
class OpportunityV01Schema(Schema):
opportunity_id = fields.Integer(
dump_only=True,
metadata={"description": "The internal ID of the opportunity", "example": 12345},
Expand Down Expand Up @@ -227,9 +227,9 @@ class OpportunitySchema(Schema):
)

opportunity_assistance_listings = fields.List(
fields.Nested(OpportunityAssistanceListingSchema())
fields.Nested(OpportunityAssistanceListingV01Schema())
)
summary = fields.Nested(OpportunitySummarySchema())
summary = fields.Nested(OpportunitySummaryV01Schema())

opportunity_status = fields.Enum(
OpportunityStatus,
Expand All @@ -243,7 +243,7 @@ class OpportunitySchema(Schema):
updated_at = fields.DateTime(dump_only=True)


class OpportunitySearchFilterSchema(Schema):
class OpportunitySearchFilterV01Schema(Schema):
funding_instrument = fields.Nested(
StrSearchSchemaBuilder("FundingInstrumentFilterSchema")
.with_one_of(allowed_values=FundingInstrument)
Expand Down Expand Up @@ -271,7 +271,7 @@ class OpportunitySearchFilterSchema(Schema):
)


class OpportunitySearchRequestSchema(Schema):
class OpportunitySearchRequestV01Schema(Schema):
query = fields.String(
metadata={
"description": "Query string which searches against several text fields",
Expand All @@ -280,7 +280,7 @@ class OpportunitySearchRequestSchema(Schema):
validate=[validators.Length(min=1, max=100)],
)

filters = fields.Nested(OpportunitySearchFilterSchema())
filters = fields.Nested(OpportunitySearchFilterV01Schema())

pagination = fields.Nested(
generate_pagination_schema(
Expand Down
6 changes: 6 additions & 0 deletions api/src/api/opportunities_v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from src.api.opportunities_v1.opportunity_blueprint import opportunity_blueprint

# import opportunity_routes module to register the API routes on the blueprint
import src.api.opportunities_v1.opportunity_routes # noqa: F401 E402 isort:skip

__all__ = ["opportunity_blueprint"]
9 changes: 9 additions & 0 deletions api/src/api/opportunities_v1/opportunity_blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from apiflask import APIBlueprint

opportunity_blueprint = APIBlueprint(
"opportunity_v1",
__name__,
tag="Opportunity v1",
cli_group="opportunity_v1",
url_prefix="/v1",
)
66 changes: 66 additions & 0 deletions api/src/api/opportunities_v1/opportunity_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import logging

import src.adapters.db as db
import src.adapters.db.flask_db as flask_db
import src.api.opportunities_v1.opportunity_schemas as opportunity_schemas
import src.api.response as response
from src.api.opportunities_v1.opportunity_blueprint import opportunity_blueprint
from src.auth.api_key_auth import api_key_auth
from src.logging.flask_logger import add_extra_data_to_current_request_logs
from src.services.opportunities_v1.get_opportunity import get_opportunity
from src.services.opportunities_v1.search_opportunities import search_opportunities
from src.util.dict_util import flatten_dict

logger = logging.getLogger(__name__)

# Descriptions in OpenAPI support markdown https://swagger.io/specification/
SHARED_ALPHA_DESCRIPTION = """
__ALPHA VERSION__
This endpoint in its current form is primarily for testing and feedback.
Features in this endpoint are still under heavy development, and subject to change. Not for production use.
See [Release Phases](https://github.com/github/roadmap?tab=readme-ov-file#release-phases) for further details.
"""


@opportunity_blueprint.post("/opportunities/search")
@opportunity_blueprint.input(
opportunity_schemas.OpportunitySearchRequestV1Schema, arg_name="search_params"
)
# many=True allows us to return a list of opportunity objects
@opportunity_blueprint.output(opportunity_schemas.OpportunityV1Schema(many=True))
@opportunity_blueprint.auth_required(api_key_auth)
@opportunity_blueprint.doc(description=SHARED_ALPHA_DESCRIPTION)
def opportunity_search(search_params: dict) -> response.ApiResponse:
add_extra_data_to_current_request_logs(flatten_dict(search_params, prefix="request.body"))
logger.info("POST /v1/opportunities/search")

opportunities, pagination_info = search_opportunities(search_params)

add_extra_data_to_current_request_logs(
{
"response.pagination.total_pages": pagination_info.total_pages,
"response.pagination.total_records": pagination_info.total_records,
}
)
logger.info("Successfully fetched opportunities")

return response.ApiResponse(
message="Success", data=opportunities, pagination_info=pagination_info
)


@opportunity_blueprint.get("/opportunities/<int:opportunity_id>")
@opportunity_blueprint.output(opportunity_schemas.OpportunityV1Schema)
@opportunity_blueprint.auth_required(api_key_auth)
@opportunity_blueprint.doc(description=SHARED_ALPHA_DESCRIPTION)
@flask_db.with_db_session()
def opportunity_get(db_session: db.Session, opportunity_id: int) -> response.ApiResponse:
add_extra_data_to_current_request_logs({"opportunity.opportunity_id": opportunity_id})
logger.info("GET /v1/opportunities/:opportunity_id")
with db_session.begin():
opportunity = get_opportunity(db_session, opportunity_id)

return response.ApiResponse(message="Success", data=opportunity)
Loading

0 comments on commit fab0d18

Please sign in to comment.