gofannon 0.25.19__py3-none-any.whl → 0.25.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ import logging
2
+ from typing import Optional, Dict, Any, List
3
+ import json
4
+
5
+ from .base import SimplerGrantsGovBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Enum values extracted from API source for definition and validation
11
+ FUNDING_INSTRUMENT_ENUM = ["grant", "cooperative_agreement", "other"]
12
+ FUNDING_CATEGORY_ENUM = [
13
+ "recovery_act", "agriculture", "arts", "business_and_commerce",
14
+ "community_development", "consumer_protection", "disaster_prevention_and_relief",
15
+ "education", "employment_labor_and_training", "energy", "environment",
16
+ "food_and_nutrition", "health", "housing", "humanities",
17
+ "information_and_statistics", "infrastructure", "internal_security",
18
+ "law_justice_and_legal_services", "natural_resources", "opportunity_zone_benefits",
19
+ "regional_development", "science_and_technology", "social_services",
20
+ "transportation", "other"
21
+ ]
22
+ APPLICANT_TYPE_ENUM = [
23
+ "state_governments", "county_governments", "city_or_township_governments",
24
+ "special_district_governments", "independent_school_districts",
25
+ "public_and_state_controlled_institutions_of_higher_education",
26
+ "indian_native_american_tribal_governments_federally_recognized",
27
+ "indian_native_american_tribal_governments_other_than_federally_recognized",
28
+ "indian_native_american_tribal_organizations_other_than_governments",
29
+ "nonprofits_having_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education",
30
+ "nonprofits_that_do_not_have_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education",
31
+ "private_institutions_of_higher_education", "individuals",
32
+ "for_profit_organizations_other_than_small_businesses", "small_businesses",
33
+ "hispanic_serving_institutions", "historically_black_colleges_and_universities",
34
+ "tribally_controlled_colleges_and_universities",
35
+ "alaska_native_and_native_hawaiian_serving_institutions",
36
+ "non_domestic_non_us_entities", "other"
37
+ ]
38
+ OPPORTUNITY_STATUS_ENUM = ["forecasted", "posted", "closed", "archived"]
39
+ ALLOWED_SORT_ORDERS = [
40
+ "relevancy", "opportunity_id", "opportunity_number", "opportunity_title",
41
+ "post_date", "close_date", "agency_code", "agency_name", "top_level_agency_name"
42
+ ]
43
+ # Internal Defaults for Pagination
44
+ DEFAULT_PAGE_OFFSET = 1
45
+ DEFAULT_PAGE_SIZE = 10 # Return fewer results by default for LLM context
46
+ DEFAULT_SORT_ORDER = [{"order_by": "opportunity_id", "sort_direction": "descending"}]
47
+
48
+ @FunctionRegistry.register
49
+ class SearchOpportunities(SimplerGrantsGovBase):
50
+ """
51
+ Tool to search for grant opportunities using the Simpler Grants Gov API.
52
+ Corresponds to the POST /v1/opportunities/search endpoint. Pagination is handled internally.
53
+ Invalid enum values provided in list filters will be omitted with a warning.
54
+ """
55
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "search_opportunities"):
56
+ super().__init__(api_key=api_key, base_url=base_url)
57
+ self.name = name
58
+
59
+ @property
60
+ def definition(self):
61
+ # Definition remains largely the same, but descriptions can emphasize valid values
62
+ return {
63
+ "type": "function",
64
+ "function": {
65
+ "name": self.name,
66
+ "description": "Search for grant opportunities based on optional criteria like query text and various filters. Returns the first page of results. Invalid filter values may be omitted.",
67
+ "parameters": {
68
+ "type": "object",
69
+ "properties": {
70
+ # --- Core Search ---
71
+ "query": {
72
+ "type": "string",
73
+ "description": "Optional. Query string which searches against several text fields (e.g., 'research', 'health')."
74
+ },
75
+ "query_operator": {
76
+ "type": "string",
77
+ "enum": ["AND", "OR"],
78
+ "description": "Optional. Query operator for combining search conditions if 'query' is provided (default: AND).",
79
+ "default": "AND"
80
+ },
81
+ # --- Elevated Filters ---
82
+ "funding_instrument": {
83
+ "type": "array",
84
+ "items": {"type": "string"}, # Don't use enum here, validate in fn
85
+ "description": f"Optional. Filter by a list of funding instrument types. Valid values: {', '.join(FUNDING_INSTRUMENT_ENUM)}"
86
+ },
87
+ "funding_category": {
88
+ "type": "array",
89
+ "items": {"type": "string"},
90
+ "description": f"Optional. Filter by a list of funding categories. Valid values: {', '.join(FUNDING_CATEGORY_ENUM)}"
91
+ },
92
+ "applicant_type": {
93
+ "type": "array",
94
+ "items": {"type": "string"},
95
+ "description": f"Optional. Filter by a list of applicant types. Valid values: {', '.join(APPLICANT_TYPE_ENUM)}"
96
+ },
97
+ "opportunity_status": {
98
+ "type": "array",
99
+ "items": {"type": "string"},
100
+ "description": f"Optional. Filter by a list of opportunity statuses. Valid values: {', '.join(OPPORTUNITY_STATUS_ENUM)}"
101
+ },
102
+ "agency": {
103
+ "type": "array",
104
+ "items": {"type": "string", "description": "Agency code, e.g., HHS, USAID"},
105
+ "description": "Optional. Filter by a list of agency codes. No strict validation applied."
106
+ },
107
+ "assistance_listing_number": {
108
+ "type": "array",
109
+ "items": {"type": "string", "pattern": r"^\d{2}\.\d{2,3}$", "description": "ALN format ##.### e.g. 45.149"},
110
+ "description": "Optional. Filter by a list of Assistance Listing Numbers (ALN / CFDA)."
111
+ },
112
+ "is_cost_sharing": {
113
+ "type": "boolean",
114
+ "description": "Optional. Filter opportunities based on whether cost sharing is required (True) or not required (False)."
115
+ },
116
+ "expected_number_of_awards": {
117
+ "type": "object",
118
+ "properties": {
119
+ "min": {"type": "integer", "minimum": 0},
120
+ "max": {"type": "integer", "minimum": 0}
121
+ },
122
+ "description": "Optional. Filter by expected number of awards range (provide 'min', 'max', or both)."
123
+ },
124
+ "award_floor": {
125
+ "type": "object",
126
+ "properties": {
127
+ "min": {"type": "integer", "minimum": 0},
128
+ "max": {"type": "integer", "minimum": 0}
129
+ },
130
+ "description": "Optional. Filter by award floor range (minimum award amount)."
131
+ },
132
+ "award_ceiling": {
133
+ "type": "object",
134
+ "properties": {
135
+ "min": {"type": "integer", "minimum": 0},
136
+ "max": {"type": "integer", "minimum": 0}
137
+ },
138
+ "description": "Optional. Filter by award ceiling range (maximum award amount)."
139
+ },
140
+ "estimated_total_program_funding": {
141
+ "type": "object",
142
+ "properties": {
143
+ "min": {"type": "integer", "minimum": 0},
144
+ "max": {"type": "integer", "minimum": 0}
145
+ },
146
+ "description": "Optional. Filter by estimated total program funding range."
147
+ },
148
+ "post_date": {
149
+ "type": "object",
150
+ "properties": {
151
+ "start_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"},
152
+ "end_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"}
153
+ },
154
+ "description": "Optional. Filter by post date range (provide 'start_date', 'end_date', or both)."
155
+ },
156
+ "close_date": {
157
+ "type": "object",
158
+ "properties": {
159
+ "start_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"},
160
+ "end_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"}
161
+ },
162
+ "description": "Optional. Filter by close date range (provide 'start_date', 'end_date', or both)."
163
+ }
164
+ },
165
+ "required": [] # No parameters are strictly required anymore
166
+ }
167
+ }
168
+ }
169
+
170
+ def _validate_and_filter_list(self, input_list: Optional[List[str]], filter_name: str, valid_enums: List[str], warnings_list: List[Dict]) -> List[str]:
171
+ """Helper to validate items in a list against enums and collect warnings."""
172
+ if input_list is None:
173
+ return []
174
+
175
+ if not isinstance(input_list, list):
176
+ # If the input is not a list, treat it as an invalid attempt and warn
177
+ warning_msg = f"Invalid type for filter '{filter_name}': Expected a list, got {type(input_list).__name__}. Filter omitted."
178
+ logger.warning(warning_msg)
179
+ warnings_list.append({"filter": filter_name, "error": warning_msg})
180
+ return []
181
+
182
+ valid_items = []
183
+ for item in input_list:
184
+ if isinstance(item, str) and item in valid_enums:
185
+ valid_items.append(item)
186
+ else:
187
+ # Log and record warning for the invalid item
188
+ warning_msg = f"Invalid value '{item}' provided for filter '{filter_name}'. It was omitted. Valid values are: {', '.join(valid_enums)}."
189
+ logger.warning(warning_msg)
190
+ warnings_list.append({"filter": filter_name, "invalid_value": item, "valid_values": valid_enums, "message": warning_msg})
191
+
192
+ return valid_items
193
+
194
+ def _add_warnings_to_response(self, response_str: str, warnings_list: List[Dict]) -> str:
195
+ """Adds a 'warnings' field to a JSON response string if warnings exist."""
196
+ if not warnings_list:
197
+ return response_str
198
+
199
+ try:
200
+ response_data = json.loads(response_str)
201
+ # Ensure response_data is a dictionary before adding warnings
202
+ if isinstance(response_data, dict):
203
+ response_data['warnings'] = warnings_list
204
+ else:
205
+ # If the response wasn't a dict (e.g., just a string error message), wrap it
206
+ logger.warning(f"Original response was not a dict, wrapping to add warnings. Original: {response_str[:100]}")
207
+ response_data = {
208
+ "original_response": response_data,
209
+ "warnings": warnings_list
210
+ }
211
+ return json.dumps(response_data)
212
+ except json.JSONDecodeError:
213
+ logger.error(f"Failed to parse response JSON to add warnings. Original response: {response_str[:500]}...")
214
+ # Return original response string if parsing fails
215
+ return response_str
216
+ except Exception as e:
217
+ logger.error(f"Unexpected error adding warnings to response: {e}", exc_info=True)
218
+ return response_str # Fallback to original
219
+
220
+ def fn(self,
221
+ query: Optional[str] = None,
222
+ query_operator: str = "AND",
223
+ # --- Elevated Filter Args ---
224
+ funding_instrument: Optional[List[str]] = None,
225
+ funding_category: Optional[List[str]] = None,
226
+ applicant_type: Optional[List[str]] = None,
227
+ opportunity_status: Optional[List[str]] = None,
228
+ agency: Optional[List[str]] = None,
229
+ assistance_listing_number: Optional[List[str]] = None,
230
+ is_cost_sharing: Optional[bool] = None,
231
+ expected_number_of_awards: Optional[Dict[str, int]] = None,
232
+ award_floor: Optional[Dict[str, int]] = None,
233
+ award_ceiling: Optional[Dict[str, int]] = None,
234
+ estimated_total_program_funding: Optional[Dict[str, int]] = None,
235
+ post_date: Optional[Dict[str, str]] = None,
236
+ close_date: Optional[Dict[str, str]] = None
237
+ ) -> str:
238
+ """
239
+ Executes the opportunity search with input validation, internal pagination,
240
+ and reconstructed filters. Adds warnings to the response for omitted invalid filter values.
241
+
242
+ Args:
243
+ (Refer to definition for descriptions)
244
+
245
+ Returns:
246
+ A JSON string representing the search results (potentially with a 'warnings' field) or an error.
247
+ """
248
+ self.logger.info(f"Executing Simpler Grants Gov opportunity search tool with query='{query}'")
249
+ warnings_list: List[Dict] = [] # Store warnings here
250
+
251
+ try:
252
+ # --- Internal Pagination ---
253
+ internal_pagination = {
254
+ "page_offset": DEFAULT_PAGE_OFFSET,
255
+ "page_size": DEFAULT_PAGE_SIZE,
256
+ "sort_order": DEFAULT_SORT_ORDER
257
+ }
258
+ self.logger.debug(f"Using internal pagination: {internal_pagination}")
259
+
260
+ # --- Reconstruct and Validate Filters for API ---
261
+ api_filters: Dict[str, Any] = {}
262
+
263
+ # Validate and filter list-based enums
264
+ valid_fi = self._validate_and_filter_list(funding_instrument, "funding_instrument", FUNDING_INSTRUMENT_ENUM, warnings_list)
265
+ if valid_fi: api_filters["funding_instrument"] = {"one_of": valid_fi}
266
+
267
+ valid_fc = self._validate_and_filter_list(funding_category, "funding_category", FUNDING_CATEGORY_ENUM, warnings_list)
268
+ if valid_fc: api_filters["funding_category"] = {"one_of": valid_fc}
269
+
270
+ valid_at = self._validate_and_filter_list(applicant_type, "applicant_type", APPLICANT_TYPE_ENUM, warnings_list)
271
+ if valid_at: api_filters["applicant_type"] = {"one_of": valid_at}
272
+
273
+ valid_os = self._validate_and_filter_list(opportunity_status, "opportunity_status", OPPORTUNITY_STATUS_ENUM, warnings_list)
274
+ if valid_os: api_filters["opportunity_status"] = {"one_of": valid_os}
275
+
276
+ # Filters without strict enum validation in this example (pass if list)
277
+ if agency is not None:
278
+ if isinstance(agency, list):
279
+ api_filters["agency"] = {"one_of": agency}
280
+ else:
281
+ warnings_list.append({"filter": "agency", "error": f"Expected a list, got {type(agency).__name__}. Filter omitted."})
282
+ logger.warning(f"Invalid type for filter 'agency'. Expected list, got {type(agency).__name__}. Filter omitted.")
283
+
284
+ if assistance_listing_number is not None:
285
+ # Add pattern validation if needed, or rely on API
286
+ if isinstance(assistance_listing_number, list):
287
+ api_filters["assistance_listing_number"] = {"one_of": assistance_listing_number}
288
+ else:
289
+ warnings_list.append({"filter": "assistance_listing_number", "error": f"Expected a list, got {type(assistance_listing_number).__name__}. Filter omitted."})
290
+ logger.warning(f"Invalid type for filter 'assistance_listing_number'. Expected list, got {type(assistance_listing_number).__name__}. Filter omitted.")
291
+
292
+
293
+ # Boolean filter
294
+ if is_cost_sharing is not None:
295
+ if isinstance(is_cost_sharing, bool):
296
+ api_filters["is_cost_sharing"] = {"one_of": [is_cost_sharing]}
297
+ else:
298
+ warnings_list.append({"filter": "is_cost_sharing", "error": f"Expected a boolean, got {type(is_cost_sharing).__name__}. Filter omitted."})
299
+ logger.warning(f"Invalid type for filter 'is_cost_sharing'. Expected boolean, got {type(is_cost_sharing).__name__}. Filter omitted.")
300
+
301
+
302
+ # Range/Date filters (basic type check)
303
+ range_date_filters = {
304
+ "expected_number_of_awards": expected_number_of_awards,
305
+ "award_floor": award_floor,
306
+ "award_ceiling": award_ceiling,
307
+ "estimated_total_program_funding": estimated_total_program_funding,
308
+ "post_date": post_date,
309
+ "close_date": close_date
310
+ }
311
+ for name, value in range_date_filters.items():
312
+ if value is not None:
313
+ if isinstance(value, dict):
314
+ api_filters[name] = value
315
+ else:
316
+ warnings_list.append({"filter": name, "error": f"Expected a dictionary object, got {type(value).__name__}. Filter omitted."})
317
+ logger.warning(f"Invalid type for filter '{name}'. Expected dict, got {type(value).__name__}. Filter omitted.")
318
+
319
+
320
+ # --- Payload Construction ---
321
+ payload: Dict[str, Any] = {
322
+ "pagination": internal_pagination,
323
+ "query_operator": query_operator
324
+ }
325
+ if query:
326
+ payload["query"] = query
327
+ if api_filters:
328
+ payload["filters"] = api_filters
329
+ self.logger.debug(f"Constructed API filters: {api_filters}")
330
+ elif not query:
331
+ # If no query and no filters, API might require something? Or just return all?
332
+ # Assuming returning all is ok. If not, add a check/error here.
333
+ self.logger.info("No query or filters provided for opportunity search.")
334
+
335
+
336
+ # --- API Call ---
337
+ endpoint = "/v1/opportunities/search"
338
+ api_response_str = self._make_request("POST", endpoint, json_payload=payload)
339
+ self.logger.debug(f"Search successful. Response length: {len(api_response_str)}")
340
+
341
+ # Add warnings to the successful response if any occurred during validation
342
+ final_response_str = self._add_warnings_to_response(api_response_str, warnings_list)
343
+ return final_response_str
344
+
345
+ except ValueError as ve: # Catch potential errors during filter reconstruction if needed
346
+ error_msg = f"Input processing failed for SearchOpportunities: {ve}"
347
+ self.logger.error(error_msg)
348
+ error_response = {"error": error_msg, "success": False}
349
+ # Add warnings even to validation error responses
350
+ if warnings_list:
351
+ error_response["warnings"] = warnings_list
352
+ return json.dumps(error_response)
353
+ except Exception as e:
354
+ error_msg = f"Opportunity search failed: {str(e)}"
355
+ self.logger.error(f"Opportunity search failed: {e}", exc_info=True)
356
+ error_response = {"error": error_msg, "success": False}
357
+ # Add warnings even to general error responses
358
+ if warnings_list:
359
+ error_response["warnings"] = warnings_list
360
+ return json.dumps(error_response)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gofannon
3
- Version: 0.25.19
3
+ Version: 0.25.21
4
4
  Summary: A collection of tools for LLMs
5
5
  License: ASFv2
6
6
  Author: Trevor Grant
@@ -52,7 +52,7 @@ functionality for various tasks.
52
52
  ## 🌟🌟 Features 🌟🌟
53
53
 
54
54
  1. Cross-Framework Compatibility (Import From/Export To Multiple Frameworks)
55
- - **Current:** `smolagents`, LangChain, AWS Bedrock
55
+ - **Current:** `smolagents`, LangChain, AWS Bedrock, Google ADK
56
56
  - **Currently Being Developed:** [Up To Date List](https://github.com/The-AI-Alliance/gofannon/issues?q=is%3Aissue%20state%3Aopen%20label%3Aframework%20assignee:*)
57
57
  - **In The Roadmap:** [Up To Date List](https://github.com/The-AI-Alliance/gofannon/issues?q=is%3Aissue%20state%3Aopen%20label%3Aframework%20no%3Aassignee)
58
58
  2. A Robust Collection of Tools
@@ -2,7 +2,8 @@ gofannon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  gofannon/arxiv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  gofannon/arxiv/get_article.py,sha256=SRGTXFXdXdXTIOLZKWUTXxZEYEqZFWFJEV2nTsU5qqU,1167
4
4
  gofannon/arxiv/search.py,sha256=37Zx1y2vAX1xYIKaAxzBGXE3qPHUZdAD2XR0H1Acs-4,4225
5
- gofannon/base/__init__.py,sha256=wnTkASBszxkZiWugQOgvSdAghFFBUwxzRzz7wTMxg3c,3757
5
+ gofannon/base/__init__.py,sha256=n2tWf7udXSXfREsxXD0E8kOY--X-ffGTNsdMwk2V33A,3814
6
+ gofannon/base/adk_mixin.py,sha256=GTvbCb-LLGPF5LUkf011lKq_JI1CB0n9K5ch_W0pAlk,13322
6
7
  gofannon/base/bedrock.py,sha256=Z2c36R8jaIusgpmegbYVz2eR7lDBc0IhTtwiqUGOcT4,25646
7
8
  gofannon/base/langchain.py,sha256=25z9opy7E7qWP-DSn6oYAqKDg8i21az-kAKrsYLfyiQ,2704
8
9
  gofannon/base/langflow.py,sha256=0WfNJ9MnndyLJ-yUAStIuXZpCzOPItsGKgtxdNifmLM,3833
@@ -15,7 +16,7 @@ gofannon/basic_math/division.py,sha256=ZO8ZzWNL9zeFdXTEdPWDpnbDrWMXVeucSu105RbQm
15
16
  gofannon/basic_math/exponents.py,sha256=w4qDlFZ9M1lf6X-tjG-ndpECfCOS7Qtc_VLICw0oh2w,1205
16
17
  gofannon/basic_math/multiplication.py,sha256=PJ5sKWMCVlBaTeZ_j3gwYOEQXAhN-qIXhnrNcyhWGKM,1168
17
18
  gofannon/basic_math/subtraction.py,sha256=gM1_N1mZ3qAXC6qnkzfijKXiOx27Gg93-CaB_ifMbOQ,1164
18
- gofannon/config.py,sha256=KPVtjBnpwfM39EQg-_xiqL1UFxcuQehrQIUS6HHBP6k,1784
19
+ gofannon/config.py,sha256=f9MybgkXSM2MDDumtxRdKPcZAdfaQ5DHGNiDJRFf_Ow,2028
19
20
  gofannon/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
21
  gofannon/file/list_directory.py,sha256=Jw0F50_hdtyDSFBmcy9e8F4KajtsPyT8Gsm_eyjMUuQ,2679
21
22
  gofannon/file/read_file.py,sha256=kYf-4aGCHLj-D-XQs0YBEnbB1MbIccgcWbiLnVXidp4,1669
@@ -47,9 +48,24 @@ gofannon/reasoning/base.py,sha256=D-4JHJqUlqgwMNOkKU0BHYA4GEWwNgPlLxKYHX0FVyg,14
47
48
  gofannon/reasoning/hierarchical_cot.py,sha256=e8ZgMbyJQ0wCBEmv7QJqFv7l3XxTMwDYupGxJ7EF6t8,11516
48
49
  gofannon/reasoning/sequential_cot.py,sha256=m9c8GnyTtmI-JntCuhkoFfULAabVOxsYgTRUd3MjzfY,3166
49
50
  gofannon/reasoning/tree_of_thought.py,sha256=TRhRJQNsFVauCLw4TOvQCDcX1nGmp_wSg9H67GJn1hs,10574
51
+ gofannon/simpler_grants_gov/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ gofannon/simpler_grants_gov/base.py,sha256=nOM6R8d02jzIh1K4FumXr2AY0dOtAMHiOnZN0WXX_oY,5639
53
+ gofannon/simpler_grants_gov/get_opportunity.py,sha256=o3dj1TtdMlI9v0no2licc3-ZL-9RnHYaVo6JLa-5Nag,2717
54
+ gofannon/simpler_grants_gov/list_agencies.py,sha256=gKQxVha72ayrsy2cOSOOmywsSjD9sSkAogJPDJgb4n8,3917
55
+ gofannon/simpler_grants_gov/query_by_applicant_eligibility.py,sha256=ixljpwWHuwOO3XNSSuGFhCdAu46eBxlKjnDTEIIuJVk,5658
56
+ gofannon/simpler_grants_gov/query_by_assistance_listing.py,sha256=NjQsNEuv4v80Z0P1cyuEWnzpfwrcit2MD8lW_tqWTjE,4011
57
+ gofannon/simpler_grants_gov/query_by_award_criteria.py,sha256=gpl60LsgXwmrBiqwk1oGHG_gY1HISrPisktVjHyedT8,5684
58
+ gofannon/simpler_grants_gov/query_by_dates.py,sha256=Eif_dJZtLDZgoDJ6cmqxl9J6UV2bA90hw-rDiAIqRD0,4881
59
+ gofannon/simpler_grants_gov/query_by_funding_details.py,sha256=AyX6aTG_mzKJIRFhbEopOrGSofWJzAhpThmCi_z5rTs,5399
60
+ gofannon/simpler_grants_gov/query_by_multiple_criteria.py,sha256=7ADabpejYx_2uNWVM9WQt3ueCwKDhIzI0zqxY61YmM8,6212
61
+ gofannon/simpler_grants_gov/query_opportunities.py,sha256=ZDB0_BQTDKLO14zNCfyfV_c8ODAMSzE6oRei0OxNfso,3342
62
+ gofannon/simpler_grants_gov/query_opportunities_by_agency.py,sha256=TsaE77ez_1hIKNR6Z26a9U7nyQspto3y5TElUWHkq_s,3906
63
+ gofannon/simpler_grants_gov/search_agencies.py,sha256=xNGqAO_xRnDHz37rUuLPEFkUhbm7XiDLN66-X_2xqt8,5062
64
+ gofannon/simpler_grants_gov/search_base.py,sha256=ask8ecAOQ9jZulr8iDxdfQZgSC_EyxDTuSVuB_FLqzc,6786
65
+ gofannon/simpler_grants_gov/search_opportunities.py,sha256=jZD64VqFIW36dBdAshOOQmywzLqBZyB3wLtA_C95sa8,19931
50
66
  gofannon/wikipedia/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
67
  gofannon/wikipedia/wikipedia_lookup.py,sha256=J6wKPbSivCF7cccaiRaJW1o0VqNhQAGfrh5U1ULLesg,2869
52
- gofannon-0.25.19.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
53
- gofannon-0.25.19.dist-info/METADATA,sha256=yzLNFL9Krw2bw3bXEEoYyGatwZRW_xFZSALJQJ3H-RI,5415
54
- gofannon-0.25.19.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
55
- gofannon-0.25.19.dist-info/RECORD,,
68
+ gofannon-0.25.21.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
+ gofannon-0.25.21.dist-info/METADATA,sha256=LMaFBOVKh41jLmEPujIquU9hJbPNv-PnYjf7Eo3qaiI,5427
70
+ gofannon-0.25.21.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
71
+ gofannon-0.25.21.dist-info/RECORD,,