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

Commit

Permalink
Merge branch 'main' into chouinar/10-populate-search-data
Browse files Browse the repository at this point in the history
  • Loading branch information
chouinar committed May 22, 2024
2 parents 49c2a2b + b40344d commit f263381
Show file tree
Hide file tree
Showing 26 changed files with 1,691 additions and 98 deletions.
740 changes: 706 additions & 34 deletions api/openapi.generated.yml

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
26 changes: 13 additions & 13 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,35 +243,35 @@ class OpportunitySchema(Schema):
updated_at = fields.DateTime(dump_only=True)


class OpportunitySearchFilterSchema(Schema):
class OpportunitySearchFilterV01Schema(Schema):
funding_instrument = fields.Nested(
StrSearchSchemaBuilder("FundingInstrumentFilterSchema")
StrSearchSchemaBuilder("FundingInstrumentFilterV01Schema")
.with_one_of(allowed_values=FundingInstrument)
.build()
)
funding_category = fields.Nested(
StrSearchSchemaBuilder("FundingCategoryFilterSchema")
StrSearchSchemaBuilder("FundingCategoryFilterV01Schema")
.with_one_of(allowed_values=FundingCategory)
.build()
)
applicant_type = fields.Nested(
StrSearchSchemaBuilder("ApplicantTypeFilterSchema")
StrSearchSchemaBuilder("ApplicantTypeFilterV01Schema")
.with_one_of(allowed_values=ApplicantType)
.build()
)
opportunity_status = fields.Nested(
StrSearchSchemaBuilder("OpportunityStatusFilterSchema")
StrSearchSchemaBuilder("OpportunityStatusFilterV01Schema")
.with_one_of(allowed_values=OpportunityStatus)
.build()
)
agency = fields.Nested(
StrSearchSchemaBuilder("AgencyFilterSchema")
StrSearchSchemaBuilder("AgencyFilterV01Schema")
.with_one_of(example="US-ABC", minimum_length=2)
.build()
)


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 f263381

Please sign in to comment.