-
Notifications
You must be signed in to change notification settings - Fork 54
[Issue #8495] Adding custom fields #8880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jcrichlake
wants to merge
8
commits into
main
Choose a base branch
from
jeff-8495-Add-custom-fields-to-openapi
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
bf75a47
Adding custom fields
jcrichlake 25083d3
Fixing mypy issues
jcrichlake c88e156
Formatting
jcrichlake cd50d2c
Adding unit tests and tranformation updates
jcrichlake 4fc8acf
Formatting and tweaking
jcrichlake df3253c
Merge branch 'main' into jeff-8495-Add-custom-fields-to-openapi
jcrichlake 1fa0f48
Adding and updating tests and addressing PR comments
jcrichlake 5593d24
Merge branch 'main' into jeff-8495-Add-custom-fields-to-openapi
jcrichlake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
api/src/api/common_grants/common_grants_custom_fields.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| """ Marshmallow schemas for CommonGrants Protocol customFields | ||
|
|
||
| This file contains Marshmallow schemas that are part of the custom field portion of the specification. | ||
|
|
||
| NOTE: Once added here the fields should be imported into another Marshmallow file to register them under the CustomFields class there. | ||
| At this time there is only one such file and that is common_grants_schemas.py which only supports Opportunity.py | ||
|
|
||
| This pattern allows for simple re-use of custom fields across different base objects since if the fields already exist | ||
| it should be as simple as importing the already existing field into the file that requires it and adding it to the CustomFields class | ||
| as a new property. | ||
| """ | ||
|
|
||
| from typing import Any | ||
|
|
||
| from src.api.schemas.extension import Schema, fields | ||
| from src.api.schemas.extension import validators as validate | ||
|
|
||
|
|
||
| class CustomFieldType(fields.String): | ||
| """Enum field for custom field types.""" | ||
|
|
||
| def __init__(self, **kwargs: Any) -> None: | ||
| super().__init__( | ||
| validate=validate.OneOf(["string", "number", "integer", "boolean", "object", "array"]), | ||
| metadata={ | ||
| "description": "The JSON schema type to use when de-serializing the value field" | ||
| }, | ||
| **kwargs | ||
| ) | ||
|
|
||
|
|
||
| class CustomField(Schema): | ||
| """Schema for defining custom fields on a record.""" | ||
|
|
||
| fieldType: fields.String | ||
| value: fields.MixinField | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "eligible_applicants"}) | ||
| fieldType = CustomFieldType(required=True) | ||
| schema = fields.URL(allow_none=True, metadata={"example": "https://example.com/schema"}) | ||
| value = fields.Raw(required=True, metadata={"example": "nonprofits, state governments"}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "The types of organizations eligible to apply"}, | ||
| ) | ||
|
|
||
|
|
||
| # =========================================================================================== | ||
| # Custom Field Implementations | ||
| # =========================================================================================== | ||
|
|
||
|
|
||
| class legacySerialId(CustomField): | ||
| """Storing legacy id for compatibility iwth legacy systems""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "legacySerialId"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "integer"}) | ||
| value = fields.Integer(required=True, metadata={"example": "12345"}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={ | ||
| "example": "An integer ID for the opportunity, needed for compatibility with legacy systems" | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class federalOpportunityNumber(CustomField): | ||
| """Federal Opportunity Number assigned to this grant opportunity""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "federalOpportunityNumber"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "string"}) | ||
| value = fields.String(required=True, metadata={"example": "ABC-123-XYZ-001"}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "The federal opportunity number assigned to this grant opportunity"}, | ||
| ) | ||
|
|
||
|
|
||
| class AssistanceListingValue(Schema): | ||
| """Schema for populating the AssistanceListing value field""" | ||
|
|
||
| assistanceListingNumber = fields.String(required=True, metadata={"example": "43.012"}) | ||
| programTitle = fields.String(required=True, metadata={"example": "Space Technology"}) | ||
|
|
||
|
|
||
| class assistanceListings(CustomField): | ||
| """The assistance listing number and program title for this opportunity""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "assistanceListings"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "array"}) | ||
| value = fields.List(fields.Nested(AssistanceListingValue), required=True) | ||
| description = fields.String( | ||
| allow_none=True, | ||
jcrichlake marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| metadata={ | ||
| "example": "The assistance listing number and program title for this opportunity" | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class AgencyValue(Schema): | ||
| """Schema for populating the Agency value field""" | ||
|
|
||
| agencyCode = fields.String(required=True, metadata={"example": "US-ABC"}) | ||
| agencyName = fields.String(allow_none=True, metadata={"example": "Department of Examples"}) | ||
| topLevelAgencyName = fields.String( | ||
| allow_none=True, metadata={"example": "Department of Examples"} | ||
| ) | ||
|
|
||
|
|
||
| class agency(CustomField): | ||
| """Information about the agency offering this opportunity""" | ||
jcrichlake marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| name = fields.String(required=True, metadata={"example": "agency"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "object"}) | ||
| value = fields.Nested(AgencyValue, required=True) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "Information about the agency offering this opportunity"}, | ||
| ) | ||
|
|
||
|
|
||
| class AttachmentValue(Schema): | ||
| downloadUrl = fields.URL(allow_none=True, metadata={"example": "https://example.com/file.pdf"}) | ||
| name = fields.String(required=True, metadata={"example": "example.pdf"}) | ||
| description = fields.String( | ||
| allow_none=True, metadata={"example": "A PDF file with instructions"} | ||
| ) | ||
| sizeInBytes = fields.Integer(required=True, metadata={"example": 1000}) | ||
| mimeType = fields.String(required=True, metadata={"example": "application/pdf"}) | ||
| createdAt = fields.DateTime(required=True, metadata={"example": "2025-01-01T17:01:01.000Z"}) | ||
| lastModifiedAt = fields.DateTime( | ||
| required=True, metadata={"example": "2025-01-02T17:30:00.000Z"} | ||
| ) | ||
|
|
||
|
|
||
| class attachments(CustomField): | ||
| """Attachments such as NOFOs or other supplemental documents""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "attachments"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "array"}) | ||
| value = fields.List(fields.Nested(AttachmentValue), required=True) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={ | ||
| "example": "Attachments such as NOFOs and supplemental documents for the opportunity" | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class federalFundingSource(CustomField): | ||
| """The category type of the grant opportunity""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "federalFundingSource"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "string"}) | ||
| value = fields.String(required=True, metadata={"example": "discretionary"}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "The category type of the grant opportunity"}, | ||
| ) | ||
|
|
||
|
|
||
| class AgencyContactValue(Schema): | ||
| """Schema for populating the AgencyContact value field""" | ||
|
|
||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "For more information, reach out to Jane Smith at agency US-ABC"}, | ||
| ) | ||
| emailAddress = fields.String(allow_none=True, metadata={"example": "fake_email@grants.gov"}) | ||
| emailDescription = fields.String( | ||
| allow_none=True, metadata={"example": "Click me to email the agency"} | ||
| ) | ||
|
|
||
|
|
||
| class contactInfo(CustomField): | ||
| """Contact information for the agency managing this opportunity""" | ||
jcrichlake marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| name = fields.String(required=True, metadata={"example": "contactInfo"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "object"}) | ||
| value = fields.Nested(AgencyContactValue, required=True) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "Contact information for the agency managing this opportunity"}, | ||
| ) | ||
|
|
||
|
|
||
| class AdditionalInfoValue(Schema): | ||
| """Schema for populating the AdditionalInfo value field""" | ||
|
|
||
| url = fields.String(allow_none=True, metadata={"example": "grants.gov"}) | ||
| description = fields.String(allow_none=True, metadata={"example": "Click me for more info"}) | ||
|
|
||
|
|
||
| class additionalInfo(CustomField): | ||
| """URL and description for additional information about the opportunity""" | ||
jcrichlake marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| name = fields.String(required=True, metadata={"example": "additionalInfo"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "object"}) | ||
| value = fields.Nested(AdditionalInfoValue, required=True) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={ | ||
| "example": "URL and description for additional information about the opportunity" | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class costSharing(CustomField): | ||
| """Whether cost sharing or matching funds are required for this opportunity""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "costSharing"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "boolean"}) | ||
| value = fields.Boolean(required=True, metadata={"example": True}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={ | ||
| "example": "Whether cost sharing or matching funds are required for this opportunity" | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class fiscalYear(CustomField): | ||
| """The fiscal year associated with this opportunity""" | ||
|
|
||
| name = fields.String(required=True, metadata={"example": "fiscalYear"}) | ||
| fieldType = fields.String(required=True, metadata={"example": "number"}) | ||
| value = fields.Integer(required=True, metadata={"example": 2026}) | ||
| description = fields.String( | ||
| allow_none=True, | ||
| metadata={"example": "The fiscal year associated with this opportunity"}, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.