From 09af0a71e5ff4495d79ed61977c108c13f08d9d3 Mon Sep 17 00:00:00 2001
From: Tariod
Date: Thu, 5 Nov 2026 15:05:04 +0100
Subject: [PATCH 1/2] chore: update openapi-generator-cli setup
---
.env | 1 -
.github/workflows/publish.yml | 174 +-
.github/workflows/test.yml | 108 +-
.gitignore | 103 +-
.openapi-generator-ignore | 17 +-
.openapi-generator/FILES | 86 -
.openapi-generator/VERSION | 1 -
README.md | 230 +-
examples/README.md | 49 +-
examples/advanced.py | 411 +-
examples/basic.py | 184 +-
openapi-config.yaml | 8 +
openapitools.json | 7 -
perigon/__init__.py | 111 -
perigon/api/__init__.py | 6 -
perigon/api/source_groups_api.py | 403 --
perigon/api/supplemental_endpoints_api.py | 873 ---
perigon/api/watchlists_api.py | 389 -
perigon/api_client.py | 80 -
perigon/models/auth_exception.py | 266 -
.../auth_exception_cause_stack_trace_inner.py | 113 -
perigon/models/illegal_parameter_exception.py | 175 -
...meter_exception_cause_stack_trace_inner.py | 113 -
perigon/models/internal_error_exception.py | 175 -
perigon/models/not_found_exception.py | 175 -
perigon/models/scored_data_article.py | 128 -
perigon/models/too_many_requests_exception.py | 266 -
...uests_exception_cause_stack_trace_inner.py | 113 -
...any_requests_exception_suppressed_inner.py | 114 -
poetry.lock | 919 ---
pyproject.toml | 116 +-
python.config.json | 10 -
scripts/generate.sh | 50 +-
scripts/preprocess_spec.py | 607 +-
scripts/smoke_test.py | 20 +
scripts/spec-filters.json | 9 +
src/perigon/__init__.py | 191 +
src/perigon/api/__init__.py | 4 +
{perigon => src/perigon}/api/v1_api.py | 6377 +++++++++++------
src/perigon/api_client.py | 178 +
{perigon => src/perigon}/api_response.py | 0
src/perigon/configuration.py | 20 +
{perigon => src/perigon}/exceptions.py | 16 +-
{perigon => src/perigon}/models/__init__.py | 88 +-
.../perigon}/models/all_endpoint_sort_by.py | 3 +-
src/perigon/models/api_key_status.py | 148 +
src/perigon/models/api_limits_dto.py | 246 +
{perigon => src/perigon}/models/article.py | 435 +-
.../perigon/models/article_news_cluster.py | 459 +-
.../perigon}/models/article_search_filter.py | 110 +-
.../perigon}/models/article_search_params.py | 37 +-
.../models/articles_vector_search_result.py | 27 +-
.../perigon}/models/category_holder.py | 20 +-
.../models/category_with_score_holder.py | 24 +-
.../perigon/models/clusters_list_sort_by.py | 9 +-
{perigon => src/perigon}/models/company.py | 33 +-
.../perigon}/models/company_count.py | 36 +-
.../perigon}/models/company_holder.py | 30 +-
.../perigon}/models/company_search_result.py | 25 +-
.../models/complex_all_endpoint_query.py | 1239 ++++
src/perigon/models/contact_point_api_dto.py | 238 +
.../models/contact_point_list_result.py | 104 +
.../models/contact_point_request_body.py | 111 +
src/perigon/models/contact_point_result.py | 95 +
src/perigon/models/contact_point_sort_by.py | 37 +
.../models/contact_point_update_body.py | 119 +
{perigon => src/perigon}/models/coordinate.py | 24 +-
.../perigon}/models/coordinate_filter.py | 26 +-
.../models/create_source_group_params.py | 12 +-
.../models/create_watchlist_params.py | 44 +-
src/perigon/models/entity_group.py | 101 +
.../perigon}/models/entity_holder.py | 29 +-
.../perigon}/models/error_response.py | 12 +-
.../perigon}/models/event_type_holder.py | 22 +-
.../perigon/models/fillbot_metadata.py | 73 +-
src/perigon/models/filled_field.py | 140 +
src/perigon/models/filled_field_source.py | 122 +
.../perigon}/models/id_name_holder.py | 24 +-
.../perigon}/models/image_holder.py | 12 +-
{perigon => src/perigon}/models/journalist.py | 262 +-
.../models/journalist_search_result.py | 25 +-
src/perigon/models/journalists_sort_by.py | 38 +
{perigon => src/perigon}/models/key_point.py | 24 +-
.../perigon}/models/keyword_holder.py | 24 +-
.../perigon}/models/label_holder.py | 20 +-
src/perigon/models/limits_result.py | 95 +
.../perigon}/models/location_count.py | 44 +-
.../perigon}/models/location_holder.py | 39 +-
src/perigon/models/matched_entity_data.py | 102 +
.../models/monitor_event_list_result.py | 104 +
src/perigon/models/monitor_list_result.py | 104 +
.../models/monitor_newsletter_list_result.py | 104 +
src/perigon/models/monitor_result.py | 95 +
.../models/monitor_summary_list_result.py | 104 +
{perigon => src/perigon}/models/name_count.py | 24 +-
src/perigon/models/news_cluster.py | 722 ++
src/perigon/models/news_cluster_article.py | 752 ++
.../models/patch_source_group_params.py | 12 +-
.../perigon}/models/people_search_result.py | 25 +-
{perigon => src/perigon}/models/person.py | 101 +-
.../perigon}/models/person_count.py | 26 +-
.../perigon}/models/person_holder.py | 22 +-
{perigon => src/perigon}/models/place.py | 86 +-
.../perigon}/models/query_search_result.py | 25 +-
{perigon => src/perigon}/models/question.py | 28 +-
.../perigon}/models/record_stat_holder.py | 24 +-
src/perigon/models/schedule_interval.py | 173 +
.../perigon/models/scored_data_article.py | 33 +-
.../perigon}/models/scored_data_wiki_data.py | 41 +-
.../perigon}/models/sentiment_holder.py | 28 +-
src/perigon/models/signal_api_create_body.py | 310 +
src/perigon/models/signal_api_dto.py | 381 +
src/perigon/models/signal_api_update_body.py | 262 +
.../models/signal_article_partial_dto.py | 149 +
src/perigon/models/signal_event_dto.py | 342 +
src/perigon/models/signal_linked_in_query.py | 141 +
.../signal_monitoring_plan_data_schema.py | 139 +
...al_monitoring_plan_data_schema_property.py | 149 +
...ignal_monitoring_plan_newsletter_config.py | 115 +
src/perigon/models/signal_newsletter_dto.py | 223 +
.../signal_newsletter_story_partial_dto.py | 150 +
src/perigon/models/signal_reddit_query.py | 104 +
.../perigon/models/signal_schedule_policy.py | 75 +-
src/perigon/models/signal_summary_dto.py | 119 +
src/perigon/models/signal_x_query.py | 106 +
.../models/social_linked_in_feed_post.py | 284 +
.../social_linked_in_feed_post_author.py | 142 +
.../social_linked_in_feed_post_comment.py | 88 +-
.../social_linked_in_feed_post_reaction.py | 92 +
.../models/social_linked_in_feed_result.py | 118 +
src/perigon/models/social_linked_in_post.py | 210 +
.../models/social_linked_in_post_author.py | 152 +
.../social_linked_in_post_search_result.py | 75 +-
src/perigon/models/social_reddit_post.py | 338 +
.../social_reddit_post_search_result.py | 118 +
src/perigon/models/social_x_post.py | 200 +
.../perigon/models/social_x_post_author.py | 43 +-
.../models/social_x_post_search_result.py | 78 +-
{perigon => src/perigon}/models/source.py | 116 +-
src/perigon/models/source_group_dto.py | 184 +
.../models/source_group_list_result.py | 104 +
.../models/source_group_resolve_result.py | 49 +-
src/perigon/models/source_group_result.py | 95 +
src/perigon/models/source_group_sort_by.py | 39 +
.../perigon}/models/source_holder.py | 30 +-
.../perigon}/models/source_location.py | 40 +-
.../perigon}/models/source_search_result.py | 25 +-
.../perigon}/models/source_top_stat_holder.py | 12 +-
src/perigon/models/sources_sort_by.py | 39 +
.../perigon}/models/stat_result.py | 40 +-
.../perigon}/models/story_history_record.py | 91 +-
.../perigon}/models/story_history_result.py | 37 +-
.../perigon}/models/story_search_result.py | 25 +-
.../perigon}/models/summary_body.py | 90 +-
.../perigon}/models/summary_search_result.py | 40 +-
.../perigon}/models/symbol_holder.py | 28 +-
{perigon => src/perigon}/models/topic_dto.py | 20 +-
.../perigon}/models/topic_holder.py | 20 +-
.../perigon}/models/topic_labels.py | 12 +-
.../perigon}/models/topic_search_result.py | 23 +-
.../models/update_watchlist_params.py | 40 +-
.../perigon}/models/watchlist_company.py | 20 +-
src/perigon/models/watchlist_company_dto.py | 135 +
src/perigon/models/watchlist_dto.py | 221 +
src/perigon/models/watchlist_list_result.py | 104 +
.../perigon}/models/watchlist_person.py | 27 +-
src/perigon/models/watchlist_person_dto.py | 127 +
.../models/watchlist_resolve_result.py | 98 +
src/perigon/models/watchlist_result.py | 95 +
src/perigon/models/watchlist_sort_by.py | 39 +
.../perigon}/models/web_resources.py | 12 +-
{perigon => src/perigon}/models/wiki_data.py | 33 +-
{perigon => src/perigon}/models/wiki_page.py | 51 +-
.../models/wiki_page_section_holder.py | 12 +-
.../perigon}/models/wikidata_date_holder.py | 12 +-
.../perigon}/models/wikidata_label_holder.py | 12 +-
.../models/wikidata_political_party_holder.py | 28 +-
.../models/wikidata_position_holder.py | 36 +-
.../models/wikipedia_search_filter.py | 63 +-
.../models/wikipedia_search_params.py | 31 +-
.../models/wikipedia_search_result.py | 25 +-
.../models/wikipedia_vector_search_result.py | 27 +-
src/perigon/models/x_post.py | 174 +
src/perigon/models/x_post_media.py | 122 +
src/perigon/models/x_post_media_item.py | 84 +
{perigon => src/perigon}/py.typed | 0
templates/README.mustache | 271 -
templates/__init__package.mustache | 4 +-
templates/api.mustache | 160 +-
templates/api_client.mustache | 234 +-
templates/configuration.mustache | 22 +
templates/pyproject.mustache | 69 -
templates/setup.mustache | 41 -
tests/__init__.py | 0
tests/conftest.py | 68 +
tests/samples.py | 221 +
tests/test_api_client.py | 106 +
tests/test_endpoints.py | 79 +
tests/test_request_encoding.py | 95 +
tests/{integration => }/test_v1api.py | 57 +-
uv.lock | 872 +++
201 files changed, 21109 insertions(+), 9696 deletions(-)
delete mode 100644 .env
delete mode 100644 .openapi-generator/FILES
delete mode 100644 .openapi-generator/VERSION
create mode 100644 openapi-config.yaml
delete mode 100644 openapitools.json
delete mode 100644 perigon/__init__.py
delete mode 100644 perigon/api/__init__.py
delete mode 100644 perigon/api/source_groups_api.py
delete mode 100644 perigon/api/supplemental_endpoints_api.py
delete mode 100644 perigon/api/watchlists_api.py
delete mode 100644 perigon/api_client.py
delete mode 100644 perigon/models/auth_exception.py
delete mode 100644 perigon/models/auth_exception_cause_stack_trace_inner.py
delete mode 100644 perigon/models/illegal_parameter_exception.py
delete mode 100644 perigon/models/illegal_parameter_exception_cause_stack_trace_inner.py
delete mode 100644 perigon/models/internal_error_exception.py
delete mode 100644 perigon/models/not_found_exception.py
delete mode 100644 perigon/models/scored_data_article.py
delete mode 100644 perigon/models/too_many_requests_exception.py
delete mode 100644 perigon/models/too_many_requests_exception_cause_stack_trace_inner.py
delete mode 100644 perigon/models/too_many_requests_exception_suppressed_inner.py
delete mode 100644 poetry.lock
delete mode 100644 python.config.json
mode change 100755 => 100644 scripts/preprocess_spec.py
create mode 100644 scripts/smoke_test.py
create mode 100644 scripts/spec-filters.json
create mode 100644 src/perigon/__init__.py
create mode 100644 src/perigon/api/__init__.py
rename {perigon => src/perigon}/api/v1_api.py (70%)
create mode 100644 src/perigon/api_client.py
rename {perigon => src/perigon}/api_response.py (100%)
create mode 100644 src/perigon/configuration.py
rename {perigon => src/perigon}/exceptions.py (96%)
rename {perigon => src/perigon}/models/__init__.py (50%)
rename {perigon => src/perigon}/models/all_endpoint_sort_by.py (98%)
create mode 100644 src/perigon/models/api_key_status.py
create mode 100644 src/perigon/models/api_limits_dto.py
rename {perigon => src/perigon}/models/article.py (63%)
rename perigon/models/news_cluster.py => src/perigon/models/article_news_cluster.py (61%)
rename {perigon => src/perigon}/models/article_search_filter.py (90%)
rename {perigon => src/perigon}/models/article_search_params.py (87%)
rename {perigon => src/perigon}/models/articles_vector_search_result.py (84%)
rename {perigon => src/perigon}/models/category_holder.py (85%)
rename {perigon => src/perigon}/models/category_with_score_holder.py (82%)
rename perigon/models/sort_by.py => src/perigon/models/clusters_list_sort_by.py (86%)
rename {perigon => src/perigon}/models/company.py (95%)
rename {perigon => src/perigon}/models/company_count.py (79%)
rename {perigon => src/perigon}/models/company_holder.py (81%)
rename {perigon => src/perigon}/models/company_search_result.py (85%)
create mode 100644 src/perigon/models/complex_all_endpoint_query.py
create mode 100644 src/perigon/models/contact_point_api_dto.py
create mode 100644 src/perigon/models/contact_point_list_result.py
create mode 100644 src/perigon/models/contact_point_request_body.py
create mode 100644 src/perigon/models/contact_point_result.py
create mode 100644 src/perigon/models/contact_point_sort_by.py
create mode 100644 src/perigon/models/contact_point_update_body.py
rename {perigon => src/perigon}/models/coordinate.py (83%)
rename {perigon => src/perigon}/models/coordinate_filter.py (83%)
rename {perigon => src/perigon}/models/create_source_group_params.py (94%)
rename {perigon => src/perigon}/models/create_watchlist_params.py (83%)
create mode 100644 src/perigon/models/entity_group.py
rename {perigon => src/perigon}/models/entity_holder.py (81%)
rename {perigon => src/perigon}/models/error_response.py (94%)
rename {perigon => src/perigon}/models/event_type_holder.py (85%)
rename perigon/models/auth_exception_cause.py => src/perigon/models/fillbot_metadata.py (58%)
create mode 100644 src/perigon/models/filled_field.py
create mode 100644 src/perigon/models/filled_field_source.py
rename {perigon => src/perigon}/models/id_name_holder.py (83%)
rename {perigon => src/perigon}/models/image_holder.py (92%)
rename {perigon => src/perigon}/models/journalist.py (62%)
rename {perigon => src/perigon}/models/journalist_search_result.py (85%)
create mode 100644 src/perigon/models/journalists_sort_by.py
rename {perigon => src/perigon}/models/key_point.py (83%)
rename {perigon => src/perigon}/models/keyword_holder.py (82%)
rename {perigon => src/perigon}/models/label_holder.py (85%)
create mode 100644 src/perigon/models/limits_result.py
rename {perigon => src/perigon}/models/location_count.py (78%)
rename {perigon => src/perigon}/models/location_holder.py (79%)
create mode 100644 src/perigon/models/matched_entity_data.py
create mode 100644 src/perigon/models/monitor_event_list_result.py
create mode 100644 src/perigon/models/monitor_list_result.py
create mode 100644 src/perigon/models/monitor_newsletter_list_result.py
create mode 100644 src/perigon/models/monitor_result.py
create mode 100644 src/perigon/models/monitor_summary_list_result.py
rename {perigon => src/perigon}/models/name_count.py (83%)
create mode 100644 src/perigon/models/news_cluster.py
create mode 100644 src/perigon/models/news_cluster_article.py
rename {perigon => src/perigon}/models/patch_source_group_params.py (95%)
rename {perigon => src/perigon}/models/people_search_result.py (85%)
rename {perigon => src/perigon}/models/person.py (80%)
rename {perigon => src/perigon}/models/person_count.py (83%)
rename {perigon => src/perigon}/models/person_holder.py (85%)
rename {perigon => src/perigon}/models/place.py (75%)
rename {perigon => src/perigon}/models/query_search_result.py (85%)
rename {perigon => src/perigon}/models/question.py (82%)
rename {perigon => src/perigon}/models/record_stat_holder.py (82%)
create mode 100644 src/perigon/models/schedule_interval.py
rename perigon/models/scored_article.py => src/perigon/models/scored_data_article.py (79%)
rename {perigon => src/perigon}/models/scored_data_wiki_data.py (70%)
rename {perigon => src/perigon}/models/sentiment_holder.py (81%)
create mode 100644 src/perigon/models/signal_api_create_body.py
create mode 100644 src/perigon/models/signal_api_dto.py
create mode 100644 src/perigon/models/signal_api_update_body.py
create mode 100644 src/perigon/models/signal_article_partial_dto.py
create mode 100644 src/perigon/models/signal_event_dto.py
create mode 100644 src/perigon/models/signal_linked_in_query.py
create mode 100644 src/perigon/models/signal_monitoring_plan_data_schema.py
create mode 100644 src/perigon/models/signal_monitoring_plan_data_schema_property.py
create mode 100644 src/perigon/models/signal_monitoring_plan_newsletter_config.py
create mode 100644 src/perigon/models/signal_newsletter_dto.py
create mode 100644 src/perigon/models/signal_newsletter_story_partial_dto.py
create mode 100644 src/perigon/models/signal_reddit_query.py
rename perigon/models/illegal_parameter_exception_suppressed_inner.py => src/perigon/models/signal_schedule_policy.py (55%)
create mode 100644 src/perigon/models/signal_summary_dto.py
create mode 100644 src/perigon/models/signal_x_query.py
create mode 100644 src/perigon/models/social_linked_in_feed_post.py
create mode 100644 src/perigon/models/social_linked_in_feed_post_author.py
rename perigon/models/too_many_requests_exception_cause.py => src/perigon/models/social_linked_in_feed_post_comment.py (51%)
create mode 100644 src/perigon/models/social_linked_in_feed_post_reaction.py
create mode 100644 src/perigon/models/social_linked_in_feed_result.py
create mode 100644 src/perigon/models/social_linked_in_post.py
create mode 100644 src/perigon/models/social_linked_in_post_author.py
rename perigon/models/illegal_parameter_exception_cause.py => src/perigon/models/social_linked_in_post_search_result.py (57%)
create mode 100644 src/perigon/models/social_reddit_post.py
create mode 100644 src/perigon/models/social_reddit_post_search_result.py
create mode 100644 src/perigon/models/social_x_post.py
rename perigon/models/vector_data.py => src/perigon/models/social_x_post_author.py (65%)
rename perigon/models/auth_exception_suppressed_inner.py => src/perigon/models/social_x_post_search_result.py (56%)
rename {perigon => src/perigon}/models/source.py (84%)
create mode 100644 src/perigon/models/source_group_dto.py
create mode 100644 src/perigon/models/source_group_list_result.py
rename perigon/models/vector_search_result.py => src/perigon/models/source_group_resolve_result.py (65%)
create mode 100644 src/perigon/models/source_group_result.py
create mode 100644 src/perigon/models/source_group_sort_by.py
rename {perigon => src/perigon}/models/source_holder.py (83%)
rename {perigon => src/perigon}/models/source_location.py (80%)
rename {perigon => src/perigon}/models/source_search_result.py (85%)
rename {perigon => src/perigon}/models/source_top_stat_holder.py (93%)
create mode 100644 src/perigon/models/sources_sort_by.py
rename {perigon => src/perigon}/models/stat_result.py (69%)
rename {perigon => src/perigon}/models/story_history_record.py (69%)
rename {perigon => src/perigon}/models/story_history_result.py (77%)
rename {perigon => src/perigon}/models/story_search_result.py (85%)
rename {perigon => src/perigon}/models/summary_body.py (72%)
rename {perigon => src/perigon}/models/summary_search_result.py (74%)
rename {perigon => src/perigon}/models/symbol_holder.py (83%)
rename {perigon => src/perigon}/models/topic_dto.py (91%)
rename {perigon => src/perigon}/models/topic_holder.py (86%)
rename {perigon => src/perigon}/models/topic_labels.py (93%)
rename {perigon => src/perigon}/models/topic_search_result.py (84%)
rename {perigon => src/perigon}/models/update_watchlist_params.py (86%)
rename {perigon => src/perigon}/models/watchlist_company.py (89%)
create mode 100644 src/perigon/models/watchlist_company_dto.py
create mode 100644 src/perigon/models/watchlist_dto.py
create mode 100644 src/perigon/models/watchlist_list_result.py
rename {perigon => src/perigon}/models/watchlist_person.py (85%)
create mode 100644 src/perigon/models/watchlist_person_dto.py
create mode 100644 src/perigon/models/watchlist_resolve_result.py
create mode 100644 src/perigon/models/watchlist_result.py
create mode 100644 src/perigon/models/watchlist_sort_by.py
rename {perigon => src/perigon}/models/web_resources.py (97%)
rename {perigon => src/perigon}/models/wiki_data.py (93%)
rename {perigon => src/perigon}/models/wiki_page.py (90%)
rename {perigon => src/perigon}/models/wiki_page_section_holder.py (95%)
rename {perigon => src/perigon}/models/wikidata_date_holder.py (93%)
rename {perigon => src/perigon}/models/wikidata_label_holder.py (93%)
rename {perigon => src/perigon}/models/wikidata_political_party_holder.py (87%)
rename {perigon => src/perigon}/models/wikidata_position_holder.py (85%)
rename {perigon => src/perigon}/models/wikipedia_search_filter.py (86%)
rename {perigon => src/perigon}/models/wikipedia_search_params.py (89%)
rename {perigon => src/perigon}/models/wikipedia_search_result.py (85%)
rename {perigon => src/perigon}/models/wikipedia_vector_search_result.py (84%)
create mode 100644 src/perigon/models/x_post.py
create mode 100644 src/perigon/models/x_post_media.py
create mode 100644 src/perigon/models/x_post_media_item.py
rename {perigon => src/perigon}/py.typed (100%)
delete mode 100644 templates/README.mustache
create mode 100644 templates/configuration.mustache
delete mode 100644 templates/pyproject.mustache
delete mode 100644 templates/setup.mustache
create mode 100644 tests/__init__.py
create mode 100644 tests/conftest.py
create mode 100644 tests/samples.py
create mode 100644 tests/test_api_client.py
create mode 100644 tests/test_endpoints.py
create mode 100644 tests/test_request_encoding.py
rename tests/{integration => }/test_v1api.py (76%)
create mode 100644 uv.lock
diff --git a/.env b/.env
deleted file mode 100644
index 25536d4..0000000
--- a/.env
+++ /dev/null
@@ -1 +0,0 @@
-PERIGON_API_KEY="1354fd4c-91dc-43fc-86d5-fcd114bfdd09"
\ No newline at end of file
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 11da876..02151c0 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,102 +1,118 @@
-name: Tag & Publish Python SDK (PyPI)
+name: Publish Python SDK
on:
workflow_dispatch:
- inputs:
- version_override:
- description: "Specify version (leave empty for auto-increment)"
- required: false
- default: ""
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}
+ cancel-in-progress: false
+
+env:
+ PYTHON_VERSION: "3.11"
+ UV_VERSION: "0.13.7"
jobs:
- tag-and-publish:
+ build:
+ if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
- permissions:
- contents: write
- id-token: write # for PyPI Trusted Publishing
+ timeout-minutes: 15
+ outputs:
+ version: ${{ steps.version.outputs.version }}
steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- # Setup Python
- - uses: actions/setup-python@v4
+ - name: Checkout
+ uses: actions/checkout@v7
with:
- python-version: "3.10"
+ persist-credentials: false
- # Install Poetry
- - uses: snok/install-poetry@v1
+ - name: Install uv
+ uses: astral-sh/setup-uv@v10.3.3
with:
- version: latest
- virtualenvs-create: false
- virtualenvs-in-project: false
+ version: ${{ env.UV_VERSION }}
+ enable-cache: true
+ python-version: ${{ env.PYTHON_VERSION }}
- - name: Get package name and determine version
- id: package_info
+ - name: Read version
+ id: version
run: |
- # Extract package name from pyproject.toml
- PACKAGE_NAME=$(poetry version -n | awk '{print $1}')
- echo "Package name: $PACKAGE_NAME"
-
- # Check if manual version override was provided
- if [[ "${{ github.event.inputs.version_override }}" != "" ]]; then
- VERSION="${{ github.event.inputs.version_override }}"
- echo "Using manually specified version: $VERSION"
- else
- # Try to get the current version from PyPI
- PYPI_VERSION=$(pip index versions $PACKAGE_NAME 2>/dev/null | grep -A1 "Available versions:" | head -1 | tr -d '[:space:]' | sed 's/Availableversions://g' | sed 's/%//g')
- echo "Current PyPI version: $PYPI_VERSION"
-
- # If PyPI version is less than 1.0.0, start at 1.0.0
- if [[ $(echo "$PYPI_VERSION" | awk -F. '{print $1}') -lt 1 ]]; then
- VERSION="1.0.0"
- echo "Starting at version 1.0.0"
- else
- # Increment the patch version
- IFS='.' read -r major minor patch <<< "$PYPI_VERSION"
- VERSION="$major.$minor.$((patch + 1))"
- echo "Incrementing to version $VERSION"
- fi
+ set -euo pipefail
+ VERSION="$(uv version --short)"
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ if [ -n "$(git ls-remote --tags origin "refs/tags/v${VERSION}")" ]; then
+ echo "Tag v${VERSION} already exists"
+ exit 1
fi
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
-
- # Update pyproject.toml version
- - name: Update pyproject.toml version
- run: poetry version ${{ steps.package_info.outputs.version }}
+ - name: Install the project
+ run: uv sync --locked --group dev
- # Install dependencies
- - name: Install dependencies
- run: poetry install
+ - name: Run unit tests
+ run: uv run pytest --unit -q
- # Build the package
- name: Build package
- run: poetry build
+ run: uv build
- # Create tag only after successful build
- - name: Create new tag
- id: create_tag
- run: |
- git config --local user.email "action@github.com"
- git config --local user.name "GitHub Action"
- git tag -a ${{ steps.package_info.outputs.tag }} -m "Release ${{ steps.package_info.outputs.tag }}"
- git push origin ${{ steps.package_info.outputs.tag }}
- echo "tag_created=true" >> "$GITHUB_OUTPUT"
+ - name: Smoke test (wheel)
+ run: uv run --isolated --no-project --with dist/*.whl scripts/smoke_test.py
- # Publish to PyPI
- - name: Publish to PyPI
- id: pypi_publish
- uses: pypa/gh-action-pypi-publish@release/v1
+ - name: Smoke test (source distribution)
+ run: uv run --isolated --no-project --with dist/*.tar.gz scripts/smoke_test.py
+
+ - name: Upload distributions
+ uses: actions/upload-artifact@v7
+ with:
+ name: dist
+ path: dist/
+
+ publish:
+ needs: build
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ environment:
+ name: pypi
+ url: https://pypi.org/p/perigon/
+ permissions:
+ id-token: write
+
+ steps:
+ - name: Install uv
+ uses: astral-sh/setup-uv@v10.3.3
+ with:
+ version: ${{ env.UV_VERSION }}
+ enable-cache: false
+
+ - name: Download distributions
+ uses: actions/download-artifact@v8
with:
- packages-dir: dist/
- verbose: true
+ name: dist
+ path: dist/
+
+ - name: Generate PEP 740 attestations
+ uses: astral-sh/attest-action@v0.0.7
+
+ - name: Publish to PyPI
+ run: uv publish --trusted-publishing always
- # Remove tag if publishing fails
- - name: Remove tag if publish failed
- if: failure() && steps.create_tag.outputs.tag_created == 'true'
+ tag:
+ needs: [build, publish]
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+
+ - name: Create tag
+ env:
+ VERSION: ${{ needs.build.outputs.version }}
run: |
- git push --delete origin ${{ steps.package_info.outputs.tag }}
- git tag -d ${{ steps.package_info.outputs.tag }}
- echo "Removed tag due to workflow failure"
+ set -euo pipefail
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git tag -a "v${VERSION}" -m "Release v${VERSION}"
+ git push origin "v${VERSION}"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 664a0d5..ea3faa4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -3,43 +3,103 @@ name: Python SDK Tests
on:
push:
branches: [main]
- paths: ["perigon/**", "tests/**"]
pull_request:
- branches: [main]
- paths: ["perigon/**", "tests/**"]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+env:
+ PYTHON_VERSION: "3.11"
+ UV_VERSION: "0.13.7"
jobs:
- test:
+ lint:
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@v4
+ - name: Checkout
+ uses: actions/checkout@v7
+ with:
+ persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v10.3.3
with:
- python-version: "3.11"
- cache: "pip"
+ version: ${{ env.UV_VERSION }}
+ enable-cache: true
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Install the project
+ run: uv sync --locked --group dev
+
+ - name: Run ruff
+ run: uv run ruff check
+
+ - name: Check formatting
+ run: uv run ruff format --check
+
+ - name: Run mypy
+ run: uv run mypy
+
+ unit:
+ needs: lint
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
- - name: Install Poetry
- uses: snok/install-poetry@v1
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v10.3.3
+ with:
+ version: ${{ env.UV_VERSION }}
+ enable-cache: true
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install the project
+ run: uv sync --locked --group dev
+
+ - name: Run unit tests
+ run: uv run pytest --unit -q
+
+ integration:
+ needs: [lint, unit]
+ if: >-
+ github.ref == 'refs/heads/main' &&
+ (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
with:
- version: 1.5.1
- virtualenvs-create: true
- virtualenvs-in-project: true
+ persist-credentials: false
- - name: Load cached dependencies
- id: cached-poetry-dependencies
- uses: actions/cache@v3
+ - name: Install uv
+ uses: astral-sh/setup-uv@v10.3.3
with:
- path: .venv
- key: ${{ runner.os }}-python-3.11-${{ hashFiles('**/poetry.lock') }}
+ version: ${{ env.UV_VERSION }}
+ enable-cache: true
+ python-version: ${{ env.PYTHON_VERSION }}
- - name: Install dependencies
- if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
- run: poetry install --no-interaction
+ - name: Install the project
+ run: uv sync --locked --group dev
- - name: Run tests
- run: poetry run pytest -q
+ - name: Run integration tests
+ run: uv run pytest --integration -q
env:
PERIGON_API_KEY: ${{ secrets.PERIGON_API_KEY }}
diff --git a/.gitignore b/.gitignore
index 032ce04..48e407c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -8,7 +9,6 @@ __pycache__/
# Distribution / packaging
.Python
-env/
build/
develop-eggs/
dist/
@@ -20,9 +20,12 @@ lib64/
parts/
sdist/
var/
+wheels/
+share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
+MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
@@ -37,17 +40,17 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
+.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
-*,cover
+*.cover
+*.py,cover
.hypothesis/
-venv/
-.venv/
-.python-version
-.pytest_cache
+.pytest_cache/
+cover/
# Translations
*.mo
@@ -55,15 +58,97 @@ venv/
# Django stuff:
*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
+.pybuilder/
target/
-#Ipython Notebook
+# Jupyter Notebook
.ipynb_checkpoints
-# OpenAPI spec preprocessing artifact (produced by scripts/preprocess_spec.py)
-/.openapi-spec.modified.json
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+.python-version
+
+# pdm
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+.idea/
+
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# OpenAPI
+.openapi-generator/
+.openapi-spec/
diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore
index 7a36be6..a9263e8 100644
--- a/.openapi-generator-ignore
+++ b/.openapi-generator-ignore
@@ -21,17 +21,6 @@
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
-perigon/rest.py
-perigon/configuration.py
-test/
-requirements.txt
-test-requirements.txt
-.github/**
-.travis.yml
-git_push.sh
-.gitlab-ci.yml
-setup.py
-setup.cfg
-tox.ini
-.github/workflows/python.yml
-.gitignore
+
+perigon_README.md
+src/perigon/rest.py
diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES
deleted file mode 100644
index 0a8aebd..0000000
--- a/.openapi-generator/FILES
+++ /dev/null
@@ -1,86 +0,0 @@
-README.md
-perigon/__init__.py
-perigon/api/__init__.py
-perigon/api/source_groups_api.py
-perigon/api/v1_api.py
-perigon/api/watchlists_api.py
-perigon/api_client.py
-perigon/api_response.py
-perigon/exceptions.py
-perigon/models/__init__.py
-perigon/models/all_endpoint_sort_by.py
-perigon/models/article.py
-perigon/models/article_search_filter.py
-perigon/models/article_search_params.py
-perigon/models/articles_vector_search_result.py
-perigon/models/category_holder.py
-perigon/models/category_with_score_holder.py
-perigon/models/company.py
-perigon/models/company_count.py
-perigon/models/company_holder.py
-perigon/models/company_search_result.py
-perigon/models/coordinate.py
-perigon/models/coordinate_filter.py
-perigon/models/create_source_group_params.py
-perigon/models/create_watchlist_params.py
-perigon/models/entity_holder.py
-perigon/models/error_response.py
-perigon/models/event_type_holder.py
-perigon/models/id_name_holder.py
-perigon/models/image_holder.py
-perigon/models/journalist.py
-perigon/models/journalist_search_result.py
-perigon/models/key_point.py
-perigon/models/keyword_holder.py
-perigon/models/label_holder.py
-perigon/models/location_count.py
-perigon/models/location_holder.py
-perigon/models/name_count.py
-perigon/models/news_cluster.py
-perigon/models/patch_source_group_params.py
-perigon/models/people_search_result.py
-perigon/models/person.py
-perigon/models/person_count.py
-perigon/models/person_holder.py
-perigon/models/place.py
-perigon/models/query_search_result.py
-perigon/models/question.py
-perigon/models/record_stat_holder.py
-perigon/models/scored_data_article.py
-perigon/models/scored_data_wiki_data.py
-perigon/models/sentiment_holder.py
-perigon/models/sort_by.py
-perigon/models/source.py
-perigon/models/source_holder.py
-perigon/models/source_location.py
-perigon/models/source_search_result.py
-perigon/models/source_top_stat_holder.py
-perigon/models/stat_result.py
-perigon/models/story_history_record.py
-perigon/models/story_history_result.py
-perigon/models/story_search_result.py
-perigon/models/summary_body.py
-perigon/models/summary_search_result.py
-perigon/models/symbol_holder.py
-perigon/models/topic_dto.py
-perigon/models/topic_holder.py
-perigon/models/topic_labels.py
-perigon/models/topic_search_result.py
-perigon/models/update_watchlist_params.py
-perigon/models/vector_data.py
-perigon/models/watchlist_company.py
-perigon/models/watchlist_person.py
-perigon/models/web_resources.py
-perigon/models/wiki_data.py
-perigon/models/wiki_page.py
-perigon/models/wiki_page_section_holder.py
-perigon/models/wikidata_date_holder.py
-perigon/models/wikidata_label_holder.py
-perigon/models/wikidata_political_party_holder.py
-perigon/models/wikidata_position_holder.py
-perigon/models/wikipedia_search_filter.py
-perigon/models/wikipedia_search_params.py
-perigon/models/wikipedia_search_result.py
-perigon/models/wikipedia_vector_search_result.py
-perigon/py.typed
-pyproject.toml
diff --git a/.openapi-generator/VERSION b/.openapi-generator/VERSION
deleted file mode 100644
index e465da4..0000000
--- a/.openapi-generator/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-7.14.0
diff --git a/README.md b/README.md
index 05ca03d..c3536a4 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
-
+
@@ -31,9 +31,10 @@
A modern, fullyβtyped Python SDK for the Perigon API, generated from the official OpenAPI specification.
-Works in **CPython 3.8+**, **PyPy**, serverless runtimes, notebooks, and async frameworks.
+Works in **CPython 3.11+**, **PyPy**, serverless runtimes, notebooks, and async frameworks.
## Table of Contents
+
@@ -44,7 +45,7 @@ Works in **CPython 3.8+**, **PyPy**, serverless runtimes, notebooks, and async f
- **Typeβhinted** request/response models powered by Pydantic
- **Async and sync support** - choose the right approach for your application
- Ships with **PEP 561 type hints** for excellent IDE integration
-- Generated directly from , so it's always in sync
+- Generated directly from , so it's always in sync
---
@@ -54,50 +55,62 @@ Works in **CPython 3.8+**, **PyPy**, serverless runtimes, notebooks, and async f
pip install perigon
# poetry add perigon
# pipx install perigon
+# uv add perigon
```
---
## π Quick start
-### 1. Instantiate the client
+### 1. Instantiate the client and make calls
```python
from perigon import V1Api, ApiClient
-# Create client with API key
-api = V1Api(ApiClient(api_key="YOUR_API_KEY"))
+# One API class owns its HTTP client and closes it on the way out.
+with V1Api(api_key="YOUR_API_KEY") as api:
+ # π Search recent news articles (sync)
+ articles = api.search_articles(q="artificial intelligence", size=5)
+ print(articles.num_results, articles.articles[0].title)
-# Alternative: environment variable or callable
-# api = V1Api(ApiClient(api_key=os.environ["PERIGON_API_KEY"]))
-# api = V1Api(ApiClient(api_key=lambda: get_api_key_from_vault()))
-```
+ # π€ Look up a journalist by ID (sync)
+ journalist = api.get_journalist_by_id(id="123456")
+ print(journalist.name)
-### 2. Make calls
+# Share one pool across API classes.
+with ApiClient(api_key="YOUR_API_KEY") as client:
+ news = V1Api(client)
-```python
-# π Search recent news articles (sync)
-articles = api.search_articles(q="artificial intelligence", size=5)
-print(articles.num_results, articles.articles[0].title)
+# Alternative: environment variable
+# import os
+# with V1Api(api_key=os.environ["PERIGON_API_KEY"]) as api:
+# ...
+```
-# π€ Look up a journalist by ID (sync)
-journalist = api.get_journalist_by_id(id="123456")
-print(journalist.name)
+### 2. Make async calls
-# π Use async variant for async applications
+```python
import asyncio
+from perigon import AsyncV1Api, AsyncApiClient
+
async def fetch_data():
- # Search articles asynchronously
- articles = await api.search_articles_async(q="technology", size=5)
-
- # Look up journalist asynchronously
- journalist = await api.get_journalist_by_id_async(id="123456")
-
- return articles, journalist
+ async with AsyncV1Api(api_key="YOUR_API_KEY") as api:
+ # Search articles asynchronously
+ articles = await api.search_articles(q="technology", size=5)
+
+ # Look up journalist asynchronously
+ journalist = await api.get_journalist_by_id(id="123456")
+
+ return articles, journalist
+
# Run in async context
articles, journalist = asyncio.run(fetch_data())
+
+# Share one async pool across API classes.
+# async with AsyncApiClient(api_key="YOUR_API_KEY") as client:
+# news = AsyncV1Api(client)
```
> All methods return **typed objects** with full IDE autocompletion support.
@@ -106,102 +119,104 @@ articles, journalist = asyncio.run(fetch_data())
## π§βπ» Endpoint examples
-### Articles β search and filter news (`/v1/all`)
+### Articles β search and filter news (`/v1/articles/all`)
-**Docs β** [https://docs.perigon.io/docs/overview](https://docs.perigon.io/docs/overview)
+**Docs β** [https://perigon.io/docs/api/intro](https://perigon.io/docs/api/intro)
```python
+from datetime import datetime
+
# Simple query
articles = api.search_articles(q="technology", size=5)
-# With date range
+# With date range. 'from' is a reserved keyword in Python, so the parameter is var_from.
articles = api.search_articles(
- q="business",
- var_from="2025-04-01", # Note: 'from' is a reserved keyword in Python
- to="2025-04-08"
+ q="business",
+ var_from=datetime(2025, 4, 1),
+ to=datetime(2025, 4, 8),
)
# Restrict to specific sources
articles = api.search_articles(source=["nytimes.com"])
```
-### Companies β fetch structured company data (`/v1/companies`)
+### Companies β fetch structured company data (`/v1/companies/all`)
-**Docs β** [https://docs.perigon.io/docs/company-data](https://docs.perigon.io/docs/company-data)
+**Docs β** [https://perigon.io/docs/api/data-model#companies](https://perigon.io/docs/api/data-model#companies)
```python
results = api.search_companies(name="Apple", size=5)
```
-### Journalists β search and detail lookβup (`/v1/journalists`)
+### Journalists β search (`/v1/journalists/all`) and detail lookβup (`/v1/journalists/{id}`)
-**Docs β** [https://docs.perigon.io/docs/journalist-data](https://docs.perigon.io/docs/journalist-data)
+**Docs β** [https://perigon.io/docs/api/data-model#journalists--authors](https://perigon.io/docs/api/data-model#journalists--authors)
```python
# Search for journalists
-results = api.search_journalists1(name="Kevin", size=1)
+results = api.search_journalists(name="Kevin", size=1)
# Get detailed information
-journalist = api.get_journalist_by_id(id=results.journalists[0].id)
+journalist = api.get_journalist_by_id(id=results.results[0].id)
```
-### Stories β discover related article clusters (`/v1/stories`)
+### Stories β discover related article clusters (`/v1/stories/all`)
-**Docs β** [https://docs.perigon.io/docs/stories-overview](https://docs.perigon.io/docs/stories-overview)
+**Docs β** [https://perigon.io/docs/api/data-model#stories](https://perigon.io/docs/api/data-model#stories)
```python
stories = api.search_stories(q="climate change", size=5)
```
-### Vector search β semantic retrieval (`/v1/vector`)
+### Vector search β semantic retrieval (`/v1/vector/news/all`)
-**Docs β** [https://docs.perigon.io/docs/vector-endpoint](https://docs.perigon.io/docs/vector-endpoint)
+**Docs β** [https://perigon.io/docs/api/vector-search](https://perigon.io/docs/api/vector-search)
```python
-from perigon.models.article_search_params import ArticleSearchParams
+from perigon.models import ArticleSearchParams
results = api.vector_search_articles(
article_search_params=ArticleSearchParams(
- prompt="Latest advancements in artificial intelligence",
- size=5
+ prompt="Latest advancements in artificial intelligence", size=5
)
)
```
-### Summarizer β generate an instant summary (`/v1/summarizer`)
+### Summarizer β generate an instant summary (`/v1/summarize`)
-**Docs β** [https://docs.perigon.io/docs/search-summarizer](https://docs.perigon.io/docs/search-summarizer)
+**Docs β** [https://perigon.io/docs/api/summarizer](https://perigon.io/docs/api/summarizer)
```python
-from perigon.models.summary_body import SummaryBody
+from perigon.models import SummaryBody
summary = api.search_summarizer(
- summary_body=SummaryBody(prompt="Key developments"),
- q="renewable energy",
- size=10
+ summary_body=SummaryBody(
+ prompt="Key developments",
+ max_article_count=10,
+ returned_article_count=10,
+ ),
+ q="renewable energy",
).summary
print(summary)
```
-### Topics β explore taxonomy (`/v1/topics`)
+### Topics β explore taxonomy (`/v1/topics/all`)
-**Docs β** [https://docs.perigon.io/docs/topics](https://docs.perigon.io/docs/topics)
+**Docs β** [https://perigon.io/docs/api/topics-types](https://perigon.io/docs/api/topics-types)
```python
topics = api.search_topics(size=10)
```
-### Wikipedia β search and filter pages (`/v1/wikipedia`)
+### Wikipedia β search and filter pages (`/v1/wikipedia/all`)
-**Docs β** [https://docs.perigon.io/docs/wikipedia](https://docs.perigon.io/docs/wikipedia)
+**Docs β** [https://perigon.io/docs/api/wikipedia](https://perigon.io/docs/api/wikipedia)
```python
# Search Wikipedia pages
wikipedia_result = api.search_wikipedia(
- q="machine learning",
- size=3,
- sort_by="relevance"
+ q="machine learning", size=3, sort_by="relevance"
)
# Filter by specific criteria
@@ -211,54 +226,55 @@ wikipedia_result = api.search_wikipedia(
)
```
-### Wikipedia vector search β semantic retrieval (`/v1/vector/wikipedia`)
+### Wikipedia vector search β semantic retrieval (`/v1/vector/wikipedia/all`)
-**Docs β** [https://docs.perigon.io/docs/vector-wikipedia](https://docs.perigon.io/docs/vector-wikipedia)
+**Docs β** [https://perigon.io/docs/api/wikipedia](https://perigon.io/docs/api/wikipedia)
```python
-from perigon.models.wikipedia_search_params import WikipediaSearchParams
+from perigon.models import WikipediaSearchParams
results = api.vector_search_wikipedia(
wikipedia_search_params=WikipediaSearchParams(
prompt="artificial intelligence and neural networks in computing",
size=3,
- pageviews_from=100
+ pageviews_from=100,
)
)
```
-| Action | Code Example |
-| --- | --- |
-| Filter by source | `api.search_articles(source=["nytimes.com"])` |
-| Limit by date range | `api.search_articles(q="business", var_from="2025-04-01", to="2025-04-08")` |
-| Company lookup | `api.search_companies(name="Apple", size=5)` |
-| Summarize any query | `api.search_summarizer(summary_body=SummaryBody(prompt="Key points"), q="renewable energy", size=20)` |
-| Semantic / vector search | `api.vector_search_articles(article_search_params=ArticleSearchParams(prompt="advancements in AI", size=5))` |
-| Retrieve available taxonomic topics | `api.search_topics(size=10)` |
-| Search Wikipedia pages | `api.search_wikipedia(q="machine learning", size=3, sort_by="relevance")` |
-| Wikipedia semantic search | `api.vector_search_wikipedia(wikipedia_search_params=WikipediaSearchParams(prompt="artificial intelligence", size=3))` |
+| Action | Code Example |
+|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
+| Filter by source | `api.search_articles(source=["nytimes.com"])` |
+| Limit by date range | `api.search_articles(q="business", var_from=datetime(2025, 4, 1), to=datetime(2025, 4, 8))` |
+| Company lookup | `api.search_companies(name="Apple", size=5)` |
+| Summarize any query | `api.search_summarizer(summary_body=SummaryBody(prompt="Key points", max_article_count=20, returned_article_count=20), q="renewable energy")` |
+| Semantic / vector search | `api.vector_search_articles(article_search_params=ArticleSearchParams(prompt="advancements in AI", size=5))` |
+| Retrieve available taxonomic topics | `api.search_topics(size=10)` |
+| Search Wikipedia pages | `api.search_wikipedia(q="machine learning", size=3, sort_by="relevance")` |
+| Wikipedia semantic search | `api.vector_search_wikipedia(wikipedia_search_params=WikipediaSearchParams(prompt="artificial intelligence", size=3))` |
---
## π Async Support
-All methods have async counterparts with the `_async` suffix:
+`AsyncV1Api` has the same method names. Each one is a coroutine:
```python
import asyncio
-from perigon import V1Api, ApiClient
+from perigon import AsyncV1Api
+
async def main():
- api = V1Api(ApiClient(api_key="YOUR_API_KEY"))
-
- # Concurrent API calls
- articles_task = api.search_articles_async(q="technology", size=5)
- journalist_task = api.get_journalist_by_id_async(id="123456")
-
- # Gather results
- articles, journalist = await asyncio.gather(articles_task, journalist_task)
-
- return articles, journalist
+ async with AsyncV1Api(api_key="YOUR_API_KEY") as api:
+ # Concurrent API calls
+ articles_task = api.search_articles(q="technology", size=5)
+ journalist_task = api.get_journalist_by_id(id="123456")
+
+ # Gather results
+ articles, journalist = await asyncio.gather(articles_task, journalist_task)
+
+ return articles, journalist
+
# Run the async function
articles, journalist = asyncio.run(main())
@@ -266,6 +282,50 @@ articles, journalist = asyncio.run(main())
---
+## π§ͺ Development
+
+The default test suite checks the client and every API method against a fake HTTP transport. It does not call the
+Perigon API and does not need `PERIGON_API_KEY`.
+
+```bash
+uv sync --group dev
+uv run pytest
+```
+
+Before publishing, run the live API checks. These call `https://api.perigon.io` and use your quota. Add `--unit` to run
+the offline tests in the same command.
+
+```bash
+export PERIGON_API_KEY="your-api-key"
+uv run pytest --integration
+uv run pytest --integration --unit
+```
+
+### Regenerate the SDK
+
+Update the version in both places before generating. They must match. `uv version` updates `pyproject.toml` and
+`uv.lock`. `packageVersion` in `openapi-config.yaml` is what the generator writes into `perigon.__version__`.
+
+```bash
+uv version 1.0.0
+```
+
+Set `packageVersion` in `openapi-config.yaml` to that same version, then regenerate:
+
+```bash
+./scripts/generate.sh
+```
+
+The script downloads the OpenAPI spec, regenerates `src/perigon`, then lints and runs the offline tests. Commit the
+result.
+
+### Publish
+
+Merge to `main` and run the Publish workflow. It reads the version from the project, tags `v{version}`, smoke-tests the
+built wheel and sdist, and publishes to PyPI. It does not change the version.
+
+---
+
## πͺͺ License
-MIT Β© Perigon
\ No newline at end of file
+MIT Β© Perigon
diff --git a/examples/README.md b/examples/README.md
index 68660c8..74d8c39 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -26,6 +26,7 @@ A beginner-friendly introduction to the Perigon SDK showing:
- Simple article search
- Company lookup
- Journalist search
+- Wikipedia search
- Basic error handling
**Run it:**
@@ -34,10 +35,10 @@ python examples/basic.py
```
**What you'll learn:**
-- How to initialize the API client
+- How to initialize `V1Api` so it owns its HTTP client
- Basic search operations
- Handling API responses
-- Working with article, company, and journalist data
+- Working with article, company, journalist, and Wikipedia data
### π Advanced Example (`advanced.py`)
@@ -47,6 +48,7 @@ A comprehensive example demonstrating sophisticated features:
- Article summarization
- Story clustering
- Topics exploration
+- Vector search over Wikipedia
- Asynchronous operations with concurrent requests
**Run it:**
@@ -83,7 +85,16 @@ python examples/advanced.py
Found 2 journalists
- John Smith
ID: 67890
- Bio: Technology journalist covering AI and machine learning developments...
+
+π Example 4: Wikipedia Search
+Searching Wikipedia pages about "machine learning"...
+
+Found 3 Wikipedia pages:
+ 1. Machine learning
+ URL: https://en.wikipedia.org/wiki/Machine_learning
+ Summary: Machine learning is a field of study in artificial intelligence...
+ Views per day: 12000
+ Last modified: 12/09/2024
β
Basic example completed!
```
@@ -109,6 +120,28 @@ python examples/advanced.py
Generated summary (10 articles analyzed):
Artificial intelligence is transforming healthcare through diagnostic tools...
+π Finding related story clusters...
+ Found 3 story clusters
+ 1. Story ID: story-123
+ Title: Cryptocurrency regulation
+ Articles in cluster: 42
+
+π·οΈ Exploring available topics...
+ Found 5 topics
+ - Technology
+ Category: Tech
+
+π§ Example 6: Vector-based Wikipedia Search
+Using semantic search for pages about artificial intelligence...
+
+Found 3 semantically related Wikipedia pages:
+ 1. Artificial intelligence
+ Relevance Score: 92.0%
+ URL: https://en.wikipedia.org/wiki/Artificial_intelligence
+ Summary: Artificial intelligence (AI) is the capability of computational systems...
+ Views per day: 50000
+ Wikidata ID: Q11660
+
π Running async operations...
Running concurrent async searches...
β Async articles search: 2 articles
@@ -122,8 +155,8 @@ python examples/advanced.py
After running these examples:
-1. **Explore the [API Documentation](https://docs.perigon.io)** for complete endpoint details
-2. **Check the test files** in `tests/integration/` for more usage patterns
+1. **Explore the [API Documentation](https://perigon.io/docs/api/intro)** for complete endpoint details
+2. **Check the test files** in `tests/` for more usage patterns
3. **Build your own application** using these examples as a foundation
4. **Consider rate limiting** and error handling for production use
@@ -135,15 +168,15 @@ After running these examples:
**Import errors:**
- Ensure you've installed the SDK: `pip install perigon`
-- Check your Python version (requires Python 3.8+)
+- Check your Python version (requires Python 3.11+)
**API errors:**
- Verify your API key is correct
- Check your account's API limits
-- Review the [API documentation](https://docs.perigon.io) for endpoint-specific requirements
+- Review the [API documentation](https://perigon.io/docs/api/intro) for endpoint-specific requirements
## Support
-- **Documentation:** [docs.perigon.io](https://docs.perigon.io)
+- **Documentation:** [perigon.io](https://perigon.io/docs/api/intro)
- **Issues:** Report bugs or request features on the project repository
- **Questions:** Check the API documentation or contact support
\ No newline at end of file
diff --git a/examples/advanced.py b/examples/advanced.py
index ed3b458..f886ab9 100644
--- a/examples/advanced.py
+++ b/examples/advanced.py
@@ -17,13 +17,20 @@
import asyncio
import os
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
-from perigon import ApiClient, V1Api
-from perigon.models import ArticleSearchParams, SummaryBody, WikipediaSearchParams
+from perigon import AsyncV1Api, V1Api
+from perigon.models import (
+ ArticleSearchParams,
+ CompanySearchResult,
+ JournalistSearchResult,
+ QuerySearchResult,
+ SummaryBody,
+ WikipediaSearchParams,
+)
-def main():
+async def main():
"""Advanced example showing sophisticated usage patterns."""
# Get API key from environment variable
@@ -33,224 +40,220 @@ def main():
print(" Example: export PERIGON_API_KEY='your-api-key-here'")
return
- # Initialize the API client
+ # Each API owns its HTTP client and closes it when the block ends.
print("π Initializing Perigon API client...")
- api = V1Api(ApiClient(api_key=api_key))
-
- # Example 1: Advanced article search with date filtering
- print("\nπ
Searching articles with date filters...")
- try:
- # Search for business news from the last 7 days
- end_date = datetime.now(timezone.utc)
- start_date = end_date - timedelta(days=7)
-
- articles_result = api.search_articles(
- q="business technology",
- var_from=start_date, # 'from' is a reserved keyword, so use 'var_from'
- to=end_date,
- size=5,
- source=["techcrunch.com", "wired.com"], # Filter by specific sources
- )
-
- print(f" Found {articles_result.num_results} articles from the last 7 days")
- for i, article in enumerate(articles_result.articles, 1):
- print(f" {i}. {article.title}")
- print(
- f" Source: {article.source.domain if article.source else 'Unknown'}"
+ with V1Api(api_key=api_key) as api:
+ # Example 1: Advanced article search with date filtering
+ print("\nπ
Searching articles with date filters...")
+ try:
+ # Search for business news from the last 7 days
+ end_date = datetime.now(UTC)
+ start_date = end_date - timedelta(days=7)
+
+ articles_result = api.search_articles(
+ q="business technology",
+ var_from=start_date, # 'from' is a reserved keyword, so use 'var_from'
+ to=end_date,
+ size=5,
+ source=["techcrunch.com", "wired.com"], # Filter by specific sources
)
- print(f" Published: {article.pub_date}")
- if article.summary:
- print(f" Summary: {article.summary[:100]}...")
- print()
-
- except Exception as e:
- print(f" β Error in advanced article search: {e}")
-
- # Example 2: Vector search for semantic similarity
- print("π Using vector search for semantic similarity...")
- try:
- search_params = ArticleSearchParams(
- prompt="Climate change impact on renewable energy adoption", size=3
- )
-
- vector_results = api.vector_search_articles(article_search_params=search_params)
-
- if vector_results.results:
- print(
- f" Found {len(vector_results.results)} semantically similar articles"
- )
- for i, article in enumerate(vector_results.results, 1):
- print(f" {i}. {article.data.title}")
- print(f" Relevance Score: {getattr(article, 'score', 'N/A')}")
- print(
- f" Source: {article.data.source.domain if article.data.source else 'Unknown'}"
- )
+
+ found = articles_result.num_results
+ print(f" Found {found} articles from the last 7 days")
+ for i, article in enumerate(articles_result.articles, 1):
+ source = article.source.domain if article.source else "Unknown"
+ print(f" {i}. {article.title}")
+ print(f" Source: {source}")
+ print(f" Published: {article.pub_date}")
+ if article.summary:
+ print(f" Summary: {article.summary[:100]}...")
print()
- else:
- print(" No results found for vector search")
-
- except Exception as e:
- print(f" β Error in vector search: {e}")
-
- # Example 3: Article summarization
- print("π Generating article summaries...")
- try:
- summary_body = SummaryBody(
- prompt="Summarize the key developments and their impact on the industry"
- )
-
- summary_result = api.search_summarizer(
- summary_body=summary_body, q="artificial intelligence healthcare", size=10
- )
-
- if summary_result.summary:
- print(
- f" Generated summary ({len(summary_result.results)} articles analyzed):"
+
+ except Exception as e:
+ print(f" β Error in advanced article search: {e}")
+
+ # Example 2: Vector search for semantic similarity
+ print("π Using vector search for semantic similarity...")
+ try:
+ search_params = ArticleSearchParams(
+ prompt="Climate change impact on renewable energy adoption", size=3
)
- print(f" {summary_result.summary}")
- print()
- else:
- print(" No summary generated")
-
- except Exception as e:
- print(f" β Error in summarization: {e}")
-
- # Example 4: Story clustering
- print("π Finding related story clusters...")
- try:
- stories_result = api.search_stories(q="cryptocurrency regulation", size=3)
-
- if stories_result.results:
- print(f" Found {len(stories_result.results)} story clusters")
- for i, story in enumerate(stories_result.results, 1):
- print(f" {i}. Story ID: {story.id}")
- if hasattr(story, "title") and story.title:
- print(f" Title: {story.title}")
- if hasattr(story, "article_count"):
- print(f" Articles in cluster: {story.article_count}")
- print()
- else:
- print(" No story clusters found")
-
- except Exception as e:
- print(f" β Error in story search: {e}")
-
- # Example 5: Topics exploration
- print("π·οΈ Exploring available topics...")
- try:
- topics_result = api.search_topics(size=5)
-
- if topics_result.data:
- print(f" Found {len(topics_result.data)} topics")
- for topic in topics_result.data:
- print(f" - {topic.name}")
- if hasattr(topic, "description") and topic.description:
- print(f" Description: {topic.description[:80]}...")
- else:
- print(" No topics found")
-
- except Exception as e:
- print(f" β Error exploring topics: {e}")
-
- # Example 6: Vector-based Wikipedia Search
- print("\nπ§ Example 6: Vector-based Wikipedia Search")
- print(
- "Using semantic search to find Wikipedia pages related to artificial intelligence...\n"
- )
-
- try:
- vector_wikipedia_result = api.vector_search_wikipedia(
- wikipedia_search_params=WikipediaSearchParams(
- prompt="artificial intelligence and neural networks in computing",
- size=3,
- pageviews_from=100, # Only pages with significant viewership
+
+ vector_results = api.vector_search_articles(
+ article_search_params=search_params
)
- )
-
- print(
- f"Found {len(vector_wikipedia_result.results)} semantically related Wikipedia pages:"
- )
- for i, result in enumerate(vector_wikipedia_result.results, 1):
- page = result.data
- print(f" {i}. {page.wiki_title or 'Untitled' if page else 'Untitled'}")
- print(
- f" Relevance Score: {(result.score * 100):.1f}%"
- if result.score
- else " Relevance Score: N/A"
+
+ if vector_results.results:
+ match_count = len(vector_results.results)
+ print(f" Found {match_count} semantically similar articles")
+ for i, match in enumerate(vector_results.results, 1):
+ data = match.data
+ title = data.title if data else "Untitled"
+ source = data.source.domain if data and data.source else "Unknown"
+ score = match.score if match.score is not None else "N/A"
+ print(f" {i}. {title}")
+ print(f" Relevance Score: {score}")
+ print(f" Source: {source}")
+ print()
+ else:
+ print(" No results found for vector search")
+
+ except Exception as e:
+ print(f" β Error in vector search: {e}")
+
+ # Example 3: Article summarization
+ print("π Generating article summaries...")
+ try:
+ summary_body = SummaryBody(
+ prompt=(
+ "Summarize the key developments and their impact on the industry"
+ ),
+ max_article_count=10,
+ returned_article_count=10,
)
- print(f" URL: {page.url if page and hasattr(page, 'url') else 'N/A'}")
- summary = "No summary available"
- if page and hasattr(page, "content") and page.content:
- summary = (
- page.content[:200] + "..."
- if len(page.content) > 200
- else page.content
+ summary_result = api.search_summarizer(
+ summary_body=summary_body,
+ q="artificial intelligence healthcare",
+ )
+
+ if summary_result.summary:
+ analyzed = len(summary_result.results)
+ print(f" Generated summary ({analyzed} articles analyzed):")
+ print(f" {summary_result.summary}")
+ print()
+ else:
+ print(" No summary generated")
+
+ except Exception as e:
+ print(f" β Error in summarization: {e}")
+
+ # Example 4: Story clustering
+ print("π Finding related story clusters...")
+ try:
+ stories_result = api.search_stories(q="cryptocurrency regulation", size=3)
+
+ if stories_result.results:
+ print(f" Found {len(stories_result.results)} story clusters")
+ for i, story in enumerate(stories_result.results, 1):
+ print(f" {i}. Story ID: {story.id}")
+ if story.name:
+ print(f" Title: {story.name}")
+ article_count = story.unique_count
+ if article_count is None:
+ article_count = story.total_count
+ if article_count is not None:
+ print(f" Articles in cluster: {article_count}")
+ print()
+ else:
+ print(" No story clusters found")
+
+ except Exception as e:
+ print(f" β Error in story search: {e}")
+
+ # Example 5: Topics exploration
+ print("π·οΈ Exploring available topics...")
+ try:
+ topics_result = api.search_topics(size=5)
+
+ if topics_result.data:
+ print(f" Found {len(topics_result.data)} topics")
+ for topic in topics_result.data:
+ print(f" - {topic.name}")
+ if topic.labels and topic.labels.category:
+ print(f" Category: {topic.labels.category}")
+ else:
+ print(" No topics found")
+
+ except Exception as e:
+ print(f" β Error exploring topics: {e}")
+
+ # Example 6: Vector-based Wikipedia Search
+ print("\nπ§ Example 6: Vector-based Wikipedia Search")
+ print("Using semantic search for pages about artificial intelligence...\n")
+
+ try:
+ vector_wikipedia_result = api.vector_search_wikipedia(
+ wikipedia_search_params=WikipediaSearchParams(
+ prompt="artificial intelligence and neural networks in computing",
+ size=3,
+ pageviews_from=100, # Only pages with significant viewership
)
- elif page and hasattr(page, "summary") and page.summary:
+ )
+
+ match_count = len(vector_wikipedia_result.results)
+ print(f"Found {match_count} semantically related Wikipedia pages:")
+ for i, result in enumerate(vector_wikipedia_result.results, 1):
+ page = result.data
+ title = page.wiki_title if page and page.wiki_title else "Untitled"
+ if result.score is None:
+ score = "N/A"
+ else:
+ score = f"{result.score * 100:.1f}%"
+ url = "N/A"
+ if page and page.wiki_title and page.wiki_code in (None, "enwiki"):
+ slug = page.wiki_title.replace(" ", "_")
+ url = f"https://en.wikipedia.org/wiki/{slug}"
summary = (
- page.summary[:200] + "..."
- if len(page.summary) > 200
- else page.summary
+ page.content if page and page.content else "No summary available"
)
- print(f" Summary: {summary}")
-
- print(f" Views per day: {page.pageviews or 'N/A' if page else 'N/A'}")
- print(f" Wikidata ID: {page.wikidata_id or 'N/A' if page else 'N/A'}\n")
-
- except Exception as e:
- print(f" β Error in vector Wikipedia search: {e}")
+ if len(summary) > 200:
+ summary = summary[:200] + "..."
+ views = page.pageviews if page and page.pageviews is not None else "N/A"
+ wikidata_id = page.wikidata_id if page and page.wikidata_id else "N/A"
+ print(f" {i}. {title}")
+ print(f" Relevance Score: {score}")
+ print(f" URL: {url}")
+ print(f" Summary: {summary}")
+ print(f" Views per day: {views}")
+ print(f" Wikidata ID: {wikidata_id}\n")
+
+ except Exception as e:
+ print(f" β Error in vector Wikipedia search: {e}")
# Example 7: Demonstrate async usage
print("\nπ Running async operations...")
- asyncio.run(async_example(api))
-
- print("β
Advanced example completed!")
-
-
-async def async_example(api: V1Api):
- """Demonstrate async operations with the Perigon SDK."""
- try:
- # Run multiple searches concurrently
- print(" Running concurrent async searches...")
-
- # Create multiple async tasks
- tasks = [
- api.search_articles_async(q="technology", size=2),
- api.search_companies_async(name="Google", size=1),
- api.search_journalists_async(name="Sarah", size=1),
- ]
-
- # Execute tasks concurrently
- results = await asyncio.gather(*tasks, return_exceptions=True)
+ async with AsyncV1Api(api_key=api_key) as api:
+ try:
+ # Run multiple searches concurrently
+ print(" Running concurrent async searches...")
+
+ # Execute searches concurrently. return_exceptions keeps one failure
+ # from cancelling the others, so each result is checked on its own.
+ (
+ async_articles,
+ async_companies,
+ async_journalists,
+ ) = await asyncio.gather(
+ api.search_articles(q="technology", size=2),
+ api.search_companies(name="Google", size=1),
+ api.search_journalists(name="Sarah", size=1),
+ return_exceptions=True,
+ )
- # Process results
- articles_result, companies_result, journalists_result = results
+ if isinstance(async_articles, QuerySearchResult):
+ article_count = len(async_articles.articles)
+ print(f" β Async articles search: {article_count} articles")
+ else:
+ print(f" β Async articles search failed: {async_articles}")
- if not isinstance(articles_result, Exception):
- print(
- f" β Async articles search: {len(articles_result.articles)} articles"
- )
- else:
- print(f" β Async articles search failed: {articles_result}")
+ if isinstance(async_companies, CompanySearchResult):
+ company_count = len(async_companies.results)
+ print(f" β Async companies search: {company_count} companies")
+ else:
+ print(f" β Async companies search failed: {async_companies}")
- if not isinstance(companies_result, Exception):
- print(
- f" β Async companies search: {len(companies_result.results)} companies"
- )
- else:
- print(f" β Async companies search failed: {companies_result}")
+ if isinstance(async_journalists, JournalistSearchResult):
+ journalist_count = len(async_journalists.results)
+ print(f" β Async journalists search: {journalist_count} journalists")
+ else:
+ print(f" β Async journalists search failed: {async_journalists}")
- if not isinstance(journalists_result, Exception):
- print(
- f" β Async journalists search: {len(journalists_result.results)} journalists"
- )
- else:
- print(f" β Async journalists search failed: {journalists_result}")
+ except Exception as e:
+ print(f" β Error in async operations: {e}")
- except Exception as e:
- print(f" β Error in async operations: {e}")
+ print("β
Advanced example completed!")
if __name__ == "__main__":
- main()
+ asyncio.run(main())
diff --git a/examples/basic.py b/examples/basic.py
index 7fca3e9..3c59f4b 100644
--- a/examples/basic.py
+++ b/examples/basic.py
@@ -14,7 +14,7 @@
import os
from datetime import datetime
-from perigon import ApiClient, V1Api
+from perigon import V1Api
def main():
@@ -27,100 +27,98 @@ def main():
print(" Example: export PERIGON_API_KEY='your-api-key-here'")
return
- # Initialize the API client
+ # V1Api owns its HTTP client and closes it when the block ends.
print("π Initializing Perigon API client...")
- api = V1Api(ApiClient(api_key=api_key))
-
- # Example 1: Search for news articles
- print("\nπ° Searching for technology news...")
- try:
- articles_result = api.search_articles(q="artificial intelligence", size=3)
- print(f" Found {articles_result.num_results} articles")
-
- for i, article in enumerate(articles_result.articles, 1):
- print(f" {i}. {article.title}")
- print(
- f" Source: {article.source.domain if article.source else 'Unknown'}"
- )
- print(f" Published: {article.pub_date}")
- print()
-
- except Exception as e:
- print(f" β Error searching articles: {e}")
-
- # Example 2: Search for companies
- print("π’ Searching for companies...")
- try:
- companies_result = api.search_companies(name="Apple", size=2)
-
- if companies_result.results:
- print(f" Found {len(companies_result.results)} companies")
- for company in companies_result.results:
- print(f" - {company.name}")
- print(f" ID: {company.id}")
- if company.description:
- print(f" Description: {company.description[:100]}...")
- print()
- else:
- print(" No companies found")
-
- except Exception as e:
- print(f" β Error searching companies: {e}")
-
- # Example 3: Search for journalists
- print("π€ Searching for journalists...")
- try:
- journalists_result = api.search_journalists(name="John", size=2)
-
- if journalists_result.results:
- print(f" Found {len(journalists_result.results)} journalists")
- for journalist in journalists_result.results:
- print(f" - {journalist.name}")
- print(f" ID: {journalist.id}")
+ with V1Api(api_key=api_key) as api:
+ # Example 1: Search for news articles
+ print("\nπ° Searching for technology news...")
+ try:
+ articles_result = api.search_articles(q="artificial intelligence", size=3)
+ print(f" Found {articles_result.num_results} articles")
+
+ for i, article in enumerate(articles_result.articles, 1):
+ source = article.source.domain if article.source else "Unknown"
+ print(f" {i}. {article.title}")
+ print(f" Source: {source}")
+ print(f" Published: {article.pub_date}")
print()
- else:
- print(" No journalists found")
-
- except Exception as e:
- print(f" β Error searching journalists: {e}")
-
- # Example 4: Wikipedia Search
- print("π Example 4: Wikipedia Search")
- print('Searching Wikipedia pages about "machine learning"...\n')
-
- try:
- wikipedia_result = api.search_wikipedia(
- q="machine learning",
- size=3,
- sort_by="relevance",
- )
-
- print(f"Found {wikipedia_result.num_results} Wikipedia pages:")
- for i, page in enumerate(wikipedia_result.results, 1):
- print(f" {i}. {page.wiki_title or 'Untitled'}")
- print(f" URL: {page.url or 'N/A'}")
- summary = page.summary if page.summary else "No summary available"
- if len(summary) > 150:
- summary = summary[:150] + "..."
- print(f" Summary: {summary}")
- print(f" Views per day: {page.pageviews or 'N/A'}")
-
- # Format the revision timestamp if available
- last_modified = "Unknown"
- if page.wiki_revision_ts:
- try:
- # Parse ISO timestamp and format as date
- dt = datetime.fromisoformat(
- page.wiki_revision_ts.replace("Z", "+00:00")
- )
- last_modified = dt.strftime("%m/%d/%Y")
- except:
- last_modified = page.wiki_revision_ts
-
- print(f" Last modified: {last_modified}\n")
-
- except Exception as e:
- print(f" β Error searching Wikipedia: {e}")
+
+ except Exception as e:
+ print(f" β Error searching articles: {e}")
+
+ # Example 2: Search for companies
+ print("π’ Searching for companies...")
+ try:
+ companies_result = api.search_companies(name="Apple", size=2)
+
+ if companies_result.results:
+ print(f" Found {len(companies_result.results)} companies")
+ for company in companies_result.results:
+ print(f" - {company.name}")
+ print(f" ID: {company.id}")
+ if company.description:
+ print(f" Description: {company.description[:100]}...")
+ print()
+ else:
+ print(" No companies found")
+
+ except Exception as e:
+ print(f" β Error searching companies: {e}")
+
+ # Example 3: Search for journalists
+ print("π€ Searching for journalists...")
+ try:
+ journalists_result = api.search_journalists(name="John", size=2)
+
+ if journalists_result.results:
+ print(f" Found {len(journalists_result.results)} journalists")
+ for journalist in journalists_result.results:
+ print(f" - {journalist.name}")
+ print(f" ID: {journalist.id}")
+ print()
+ else:
+ print(" No journalists found")
+
+ except Exception as e:
+ print(f" β Error searching journalists: {e}")
+
+ # Example 4: Wikipedia Search
+ print("π Example 4: Wikipedia Search")
+ print('Searching Wikipedia pages about "machine learning"...\n')
+
+ try:
+ wikipedia_result = api.search_wikipedia(
+ q="machine learning",
+ size=3,
+ sort_by="relevance",
+ )
+
+ print(f"Found {wikipedia_result.num_results} Wikipedia pages:")
+ for i, page in enumerate(wikipedia_result.results, 1):
+ print(f" {i}. {page.wiki_title or 'Untitled'}")
+ print(f" URL: {page.url or 'N/A'}")
+ summary = page.summary if page.summary else "No summary available"
+ if len(summary) > 150:
+ summary = summary[:150] + "..."
+ print(f" Summary: {summary}")
+ print(f" Views per day: {page.pageviews or 'N/A'}")
+
+ # Format the revision timestamp if available
+ last_modified = "Unknown"
+ if page.wiki_revision_ts:
+ try:
+ # Parse ISO timestamp and format as date
+ dt = datetime.fromisoformat(
+ page.wiki_revision_ts.replace("Z", "+00:00")
+ )
+ last_modified = dt.strftime("%m/%d/%Y")
+ except ValueError:
+ last_modified = page.wiki_revision_ts
+
+ print(f" Last modified: {last_modified}\n")
+
+ except Exception as e:
+ print(f" β Error searching Wikipedia: {e}")
print("β
Basic example completed!")
diff --git a/openapi-config.yaml b/openapi-config.yaml
new file mode 100644
index 0000000..38e4612
--- /dev/null
+++ b/openapi-config.yaml
@@ -0,0 +1,8 @@
+generateSourceCodeOnly: true
+datetimeFormat: "%Y-%m-%dT%H:%M:%S"
+library: httpx
+packageName: perigon
+packageUrl: https://github.com/goperigon/perigon-python
+packageVersion: 2.0.0
+projectName: perigon
+sourceFolder: src
diff --git a/openapitools.json b/openapitools.json
deleted file mode 100644
index 151c200..0000000
--- a/openapitools.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
- "spaces": 2,
- "generator-cli": {
- "version": "7.13.0"
- }
-}
diff --git a/perigon/__init__.py b/perigon/__init__.py
deleted file mode 100644
index 4c35679..0000000
--- a/perigon/__init__.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# coding: utf-8
-
-# flake8: noqa
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-__version__ = "1.0.0"
-
-# import apis into sdk package
-from perigon.api.source_groups_api import SourceGroupsApi
-from perigon.api.v1_api import V1Api
-from perigon.api.watchlists_api import WatchlistsApi
-from perigon.api_client import ApiClient
-
-# import ApiClient
-from perigon.api_response import ApiResponse
-from perigon.exceptions import (
- ApiAttributeError,
- ApiException,
- ApiKeyError,
- ApiTypeError,
- ApiValueError,
- OpenApiException,
-)
-
-# import models into sdk package
-from perigon.models.all_endpoint_sort_by import AllEndpointSortBy
-from perigon.models.article import Article
-from perigon.models.article_search_filter import ArticleSearchFilter
-from perigon.models.article_search_params import ArticleSearchParams
-from perigon.models.articles_vector_search_result import ArticlesVectorSearchResult
-from perigon.models.category_holder import CategoryHolder
-from perigon.models.category_with_score_holder import CategoryWithScoreHolder
-from perigon.models.company import Company
-from perigon.models.company_count import CompanyCount
-from perigon.models.company_holder import CompanyHolder
-from perigon.models.company_search_result import CompanySearchResult
-from perigon.models.coordinate import Coordinate
-from perigon.models.coordinate_filter import CoordinateFilter
-from perigon.models.create_source_group_params import CreateSourceGroupParams
-from perigon.models.create_watchlist_params import CreateWatchlistParams
-from perigon.models.entity_holder import EntityHolder
-from perigon.models.error_response import ErrorResponse
-from perigon.models.event_type_holder import EventTypeHolder
-from perigon.models.id_name_holder import IdNameHolder
-from perigon.models.image_holder import ImageHolder
-from perigon.models.journalist import Journalist
-from perigon.models.journalist_search_result import JournalistSearchResult
-from perigon.models.key_point import KeyPoint
-from perigon.models.keyword_holder import KeywordHolder
-from perigon.models.label_holder import LabelHolder
-from perigon.models.location_count import LocationCount
-from perigon.models.location_holder import LocationHolder
-from perigon.models.name_count import NameCount
-from perigon.models.news_cluster import NewsCluster
-from perigon.models.patch_source_group_params import PatchSourceGroupParams
-from perigon.models.people_search_result import PeopleSearchResult
-from perigon.models.person import Person
-from perigon.models.person_count import PersonCount
-from perigon.models.person_holder import PersonHolder
-from perigon.models.place import Place
-from perigon.models.query_search_result import QuerySearchResult
-from perigon.models.question import Question
-from perigon.models.record_stat_holder import RecordStatHolder
-from perigon.models.scored_data_article import ScoredDataArticle
-from perigon.models.scored_data_wiki_data import ScoredDataWikiData
-from perigon.models.sentiment_holder import SentimentHolder
-from perigon.models.sort_by import SortBy
-from perigon.models.source import Source
-from perigon.models.source_holder import SourceHolder
-from perigon.models.source_location import SourceLocation
-from perigon.models.source_search_result import SourceSearchResult
-from perigon.models.source_top_stat_holder import SourceTopStatHolder
-from perigon.models.stat_result import StatResult
-from perigon.models.story_history_record import StoryHistoryRecord
-from perigon.models.story_history_result import StoryHistoryResult
-from perigon.models.story_search_result import StorySearchResult
-from perigon.models.summary_body import SummaryBody
-from perigon.models.summary_search_result import SummarySearchResult
-from perigon.models.symbol_holder import SymbolHolder
-from perigon.models.topic_dto import TopicDto
-from perigon.models.topic_holder import TopicHolder
-from perigon.models.topic_labels import TopicLabels
-from perigon.models.topic_search_result import TopicSearchResult
-from perigon.models.update_watchlist_params import UpdateWatchlistParams
-from perigon.models.vector_data import VectorData
-from perigon.models.watchlist_company import WatchlistCompany
-from perigon.models.watchlist_person import WatchlistPerson
-from perigon.models.web_resources import WebResources
-from perigon.models.wiki_data import WikiData
-from perigon.models.wiki_page import WikiPage
-from perigon.models.wiki_page_section_holder import WikiPageSectionHolder
-from perigon.models.wikidata_date_holder import WikidataDateHolder
-from perigon.models.wikidata_label_holder import WikidataLabelHolder
-from perigon.models.wikidata_political_party_holder import WikidataPoliticalPartyHolder
-from perigon.models.wikidata_position_holder import WikidataPositionHolder
-from perigon.models.wikipedia_search_filter import WikipediaSearchFilter
-from perigon.models.wikipedia_search_params import WikipediaSearchParams
-from perigon.models.wikipedia_search_result import WikipediaSearchResult
-from perigon.models.wikipedia_vector_search_result import WikipediaVectorSearchResult
diff --git a/perigon/api/__init__.py b/perigon/api/__init__.py
deleted file mode 100644
index cc12c2d..0000000
--- a/perigon/api/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# flake8: noqa
-
-# import apis into api package
-from perigon.api.source_groups_api import SourceGroupsApi
-from perigon.api.v1_api import V1Api
-from perigon.api.watchlists_api import WatchlistsApi
diff --git a/perigon/api/source_groups_api.py b/perigon/api/source_groups_api.py
deleted file mode 100644
index 32b7575..0000000
--- a/perigon/api/source_groups_api.py
+++ /dev/null
@@ -1,403 +0,0 @@
-from enum import Enum
-from typing import Any, Dict, Iterable, List, Mapping, Optional
-
-from pydantic import Field, StrictInt, StrictStr
-from typing_extensions import Annotated
-
-from perigon.api_client import ApiClient
-from perigon.models.create_source_group_params import CreateSourceGroupParams
-from perigon.models.patch_source_group_params import PatchSourceGroupParams
-
-# Define API paths
-PATH_CREATE_SOURCE_GROUP = "/v1/api/sourceGroups"
-PATH_DELETE_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
-PATH_GET_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
-PATH_LIST_SOURCE_GROUPS = "/v1/api/sourceGroups"
-PATH_RESOLVE_SOURCE_GROUPS = "/v1/api/sourceGroups/resolve"
-PATH_UPDATE_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
-
-
-def _normalise_query(params: Mapping[str, Any]) -> Dict[str, Any]:
- """
- β’ Convert Enum β Enum.value
- β’ Convert list/tuple/set β CSV string (after Enum handling)
- β’ Skip None values
- """
- out: Dict[str, Any] = {}
- for key, value in params.items():
- if value is None: # ignore "unset"
- continue
-
- # Unwrap single Enum
- if isinstance(value, Enum): # Enum β str
- value = value.value
-
- # Handle datetime objects properly
- from datetime import datetime
-
- if isinstance(value, datetime):
- value = value.isoformat().split("+")[0]
-
- # Handle collection (after possible Enum unwrap)
- elif isinstance(value, (list, tuple, set)):
- # unwrap Enum members inside the collection
- items: Iterable[str] = (
- (
- item.isoformat().replace(" ", "+")
- if isinstance(item, datetime)
- else str(item.value if isinstance(item, Enum) else item)
- )
- for item in value
- )
- value = ",".join(items) # CSV join
- else:
- value = str(value)
-
- out[key] = value
-
- return out
-
-
-class SourceGroupsApi:
- """"""
-
- def __init__(self, api_client: Optional[ApiClient] = None):
- self.api_client = api_client or ApiClient()
-
- # ----------------- create_source_group (sync) ----------------- #
- def create_source_group(self, create_source_group_params: CreateSourceGroupParams):
- """
- Create a new source group under the organization associated with the API key.
-
- Args:
- create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_CREATE_SOURCE_GROUP
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "POST",
- path,
- params=params,
- json=create_source_group_params.model_dump(
- by_alias=True, exclude_none=True
- ),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- create_source_group (async) ----------------- #
- async def create_source_group_async(
- self, create_source_group_params: CreateSourceGroupParams
- ):
- """
- Async variant of create_source_group. Create a new source group under the organization associated with the API key.
-
- Args:
- create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_CREATE_SOURCE_GROUP
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async(
- "POST",
- path,
- params=params,
- json=create_source_group_params.model_dump(
- by_alias=True, exclude_none=True
- ),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- delete_source_group (sync) ----------------- #
- def delete_source_group(self, id: int):
- """
- Delete a source group owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_DELETE_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request("DELETE", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- delete_source_group (async) ----------------- #
- async def delete_source_group_async(self, id: int):
- """
- Async variant of delete_source_group. Delete a source group owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_DELETE_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("DELETE", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- get_source_group (sync) ----------------- #
- def get_source_group(self, id: int):
- """
- Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_GET_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- get_source_group (async) ----------------- #
- async def get_source_group_async(self, id: int):
- """
- Async variant of get_source_group. Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_GET_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- list_source_groups (sync) ----------------- #
- def list_source_groups(
- self,
- sort_by: str,
- sort_order: str,
- name: Optional[str] = None,
- domain: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
- """
- List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
-
- Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Parameter name
- domain (Optional[str]): Parameter domain
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
-
- """
- # Get path template from class attribute
- path = PATH_LIST_SOURCE_GROUPS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if domain is not None:
- params["domain"] = domain
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
- if sort_order is not None:
- params["sortOrder"] = sort_order
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- list_source_groups (async) ----------------- #
- async def list_source_groups_async(
- self,
- sort_by: str,
- sort_order: str,
- name: Optional[str] = None,
- domain: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
- """
- Async variant of list_source_groups. List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
-
- Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Parameter name
- domain (Optional[str]): Parameter domain
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
-
- """
- # Get path template from class attribute
- path = PATH_LIST_SOURCE_GROUPS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if domain is not None:
- params["domain"] = domain
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
- if sort_order is not None:
- params["sortOrder"] = sort_order
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_source_groups (sync) ----------------- #
- def resolve_source_groups(self, name: Optional[List[str]] = None):
- """
- Resolve source groups by name. For each name, returns the organization's private source group if one exists, otherwise falls back to the matching public source group.
-
- Args:
- name (Optional[List[str]]): Source group names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_SOURCE_GROUPS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_source_groups (async) ----------------- #
- async def resolve_source_groups_async(self, name: Optional[List[str]] = None):
- """
- Async variant of resolve_source_groups. Resolve source groups by name. For each name, returns the organization's private source group if one exists, otherwise falls back to the matching public source group.
-
- Args:
- name (Optional[List[str]]): Source group names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_SOURCE_GROUPS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- update_source_group (sync) ----------------- #
- def update_source_group(
- self, id: int, patch_source_group_params: PatchSourceGroupParams
- ):
- """
- Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
-
- Args:
- id (int): Parameter id (required)
- patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_UPDATE_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "PATCH",
- path,
- params=params,
- json=patch_source_group_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- update_source_group (async) ----------------- #
- async def update_source_group_async(
- self, id: int, patch_source_group_params: PatchSourceGroupParams
- ):
- """
- Async variant of update_source_group. Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
-
- Args:
- id (int): Parameter id (required)
- patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_UPDATE_SOURCE_GROUP
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async(
- "PATCH",
- path,
- params=params,
- json=patch_source_group_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
diff --git a/perigon/api/supplemental_endpoints_api.py b/perigon/api/supplemental_endpoints_api.py
deleted file mode 100644
index 9ea085f..0000000
--- a/perigon/api/supplemental_endpoints_api.py
+++ /dev/null
@@ -1,873 +0,0 @@
-from datetime import datetime
-from enum import Enum
-from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
-
-from pydantic import Field, StrictBool, StrictInt, StrictStr
-from typing_extensions import Annotated
-
-from perigon.api_client import ApiClient
-from perigon.models.company_search_result import CompanySearchResult
-from perigon.models.journalist import Journalist
-from perigon.models.journalist_search_result import JournalistSearchResult
-from perigon.models.people_search_result import PeopleSearchResult
-from perigon.models.sort_by import SortBy
-from perigon.models.source_search_result import SourceSearchResult
-from perigon.models.topic_search_result import TopicSearchResult
-
-# Define API paths
-PATH_GET_JOURNALIST_BY_ID = "/v1/journalists/{id}"
-PATH_SEARCH_COMPANIES = "/v1/companies/all"
-PATH_SEARCH_JOURNALISTS = "/v1/journalists/all"
-PATH_SEARCH_PEOPLE = "/v1/people/all"
-PATH_SEARCH_SOURCES = "/v1/sources/all"
-PATH_SEARCH_TOPICS = "/v1/topics/all"
-
-
-def _normalise_query(params: Mapping[str, Any]) -> Dict[str, Any]:
- """
- β’ Convert Enum β Enum.value
- β’ Convert list/tuple/set β CSV string (after Enum handling)
- β’ Skip None values
- """
- out: Dict[str, Any] = {}
- for key, value in params.items():
- if value is None: # ignore "unset"
- continue
-
- # Unwrap single Enum
- if isinstance(value, Enum): # Enum β str
- value = value.value
-
- # Handle datetime objects properly
- from datetime import datetime
-
- if isinstance(value, datetime):
- value = value.isoformat().split("+")[0]
-
- # Handle collection (after possible Enum unwrap)
- elif isinstance(value, (list, tuple, set)):
- # unwrap Enum members inside the collection
- items: Iterable[str] = (
- (
- item.isoformat().replace(" ", "+")
- if isinstance(item, datetime)
- else str(item.value if isinstance(item, Enum) else item)
- )
- for item in value
- )
- value = ",".join(items) # CSV join
- else:
- value = str(value)
-
- out[key] = value
-
- return out
-
-
-class SupplementalEndpointsApi:
- """"""
-
- def __init__(self, api_client: Optional[ApiClient] = None):
- self.api_client = api_client or ApiClient()
-
- # ----------------- get_journalist_by_id (sync) ----------------- #
- def get_journalist_by_id(self, id: str) -> Journalist:
- """
- Find additional details on a journalist by using the journalist ID found in an article response object.
-
- Args:
- id (str): Parameter id (required)
-
- Returns:
- Journalist: The response
- """
- # Get path template from class attribute
- path = PATH_GET_JOURNALIST_BY_ID
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return Journalist.model_validate(resp.json())
-
- # ----------------- get_journalist_by_id (async) ----------------- #
- async def get_journalist_by_id_async(self, id: str) -> Journalist:
- """
- Async variant of get_journalist_by_id. Find additional details on a journalist by using the journalist ID found in an article response object.
-
- Args:
- id (str): Parameter id (required)
-
- Returns:
- Journalist: The response
- """
- # Get path template from class attribute
- path = PATH_GET_JOURNALIST_BY_ID
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return Journalist.model_validate(resp.json())
-
- # ----------------- search_companies (sync) ----------------- #
- def search_companies(
- self,
- id: Optional[List[str]] = None,
- symbol: Optional[List[str]] = None,
- domain: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exchange: Optional[List[str]] = None,
- num_employees_from: Optional[int] = None,
- num_employees_to: Optional[int] = None,
- ipo_from: Optional[datetime] = None,
- ipo_to: Optional[datetime] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- industry: Optional[str] = None,
- sector: Optional[str] = None,
- size: Optional[int] = None,
- page: Optional[int] = None,
- ) -> CompanySearchResult:
- """
- Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
-
- Args:
- id (Optional[List[str]]): Filter by unique company identifiers. Multiple values create an OR filter.
- symbol (Optional[List[str]]): Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
- domain (Optional[List[str]]): Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
- country (Optional[List[str]]): Filter by company headquarters country. Multiple values create an OR filter.
- exchange (Optional[List[str]]): Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
- num_employees_from (Optional[int]): Filter for companies with at least this many employees.
- num_employees_to (Optional[int]): Filter for companies with no more than this many employees.
- ipo_from (Optional[datetime]): Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
- ipo_to (Optional[datetime]): Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
- q (Optional[str]): Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- industry (Optional[str]): Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- sector (Optional[str]): Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- size (Optional[int]): The number of companies to return per page in the paginated response.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
-
- Returns:
- CompanySearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_COMPANIES
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if symbol is not None:
- params["symbol"] = symbol
- if domain is not None:
- params["domain"] = domain
- if country is not None:
- params["country"] = country
- if exchange is not None:
- params["exchange"] = exchange
- if num_employees_from is not None:
- params["numEmployeesFrom"] = num_employees_from
- if num_employees_to is not None:
- params["numEmployeesTo"] = num_employees_to
- if ipo_from is not None:
- params["ipoFrom"] = ipo_from
- if ipo_to is not None:
- params["ipoTo"] = ipo_to
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if industry is not None:
- params["industry"] = industry
- if sector is not None:
- params["sector"] = sector
- if size is not None:
- params["size"] = size
- if page is not None:
- params["page"] = page
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return CompanySearchResult.model_validate(resp.json())
-
- # ----------------- search_companies (async) ----------------- #
- async def search_companies_async(
- self,
- id: Optional[List[str]] = None,
- symbol: Optional[List[str]] = None,
- domain: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exchange: Optional[List[str]] = None,
- num_employees_from: Optional[int] = None,
- num_employees_to: Optional[int] = None,
- ipo_from: Optional[datetime] = None,
- ipo_to: Optional[datetime] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- industry: Optional[str] = None,
- sector: Optional[str] = None,
- size: Optional[int] = None,
- page: Optional[int] = None,
- ) -> CompanySearchResult:
- """
- Async variant of search_companies. Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
-
- Args:
- id (Optional[List[str]]): Filter by unique company identifiers. Multiple values create an OR filter.
- symbol (Optional[List[str]]): Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
- domain (Optional[List[str]]): Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
- country (Optional[List[str]]): Filter by company headquarters country. Multiple values create an OR filter.
- exchange (Optional[List[str]]): Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
- num_employees_from (Optional[int]): Filter for companies with at least this many employees.
- num_employees_to (Optional[int]): Filter for companies with no more than this many employees.
- ipo_from (Optional[datetime]): Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
- ipo_to (Optional[datetime]): Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
- q (Optional[str]): Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- industry (Optional[str]): Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- sector (Optional[str]): Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- size (Optional[int]): The number of companies to return per page in the paginated response.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
-
- Returns:
- CompanySearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_COMPANIES
-
- params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if symbol is not None:
- params["symbol"] = symbol
- if domain is not None:
- params["domain"] = domain
- if country is not None:
- params["country"] = country
- if exchange is not None:
- params["exchange"] = exchange
- if num_employees_from is not None:
- params["numEmployeesFrom"] = num_employees_from
- if num_employees_to is not None:
- params["numEmployeesTo"] = num_employees_to
- if ipo_from is not None:
- params["ipoFrom"] = ipo_from
- if ipo_to is not None:
- params["ipoTo"] = ipo_to
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if industry is not None:
- params["industry"] = industry
- if sector is not None:
- params["sector"] = sector
- if size is not None:
- params["size"] = size
- if page is not None:
- params["page"] = page
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return CompanySearchResult.model_validate(resp.json())
-
- # ----------------- search_journalists (sync) ----------------- #
- def search_journalists(
- self,
- id: Optional[List[str]] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- twitter: Optional[str] = None,
- size: Optional[int] = None,
- page: Optional[int] = None,
- source: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- updated_at_from: Optional[datetime] = None,
- updated_at_to: Optional[datetime] = None,
- show_num_results: Optional[bool] = None,
- ) -> JournalistSearchResult:
- """
- Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
-
- Args:
- id (Optional[List[str]]): Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
- q (Optional[str]): Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- twitter (Optional[str]): Filter journalists by their exact Twitter handle, without the @ symbol.
- size (Optional[int]): The number of journalists to return per page in the paginated response.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- source (Optional[List[str]]): Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- topic (Optional[List[str]]): Filter journalists by the topics they frequently cover. Multiple values create an OR filter to find journalists covering any of the specified topics.
- category (Optional[List[str]]): Filter journalists by the content categories they typically write about (e.g., Politics, Tech, Sports, Business). Multiple values create an OR filter.
- label (Optional[List[str]]): Filter journalists by the type of content they typically produce (e.g., Opinion, Paid-news, Non-news). Multiple values create an OR filter.
- min_monthly_posts (Optional[int]): Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
- max_monthly_posts (Optional[int]): Filter for journalists who publish no more than this many articles per month.
- country (Optional[List[str]]): Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
- updated_at_from (Optional[datetime]): Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- updated_at_to (Optional[datetime]): Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
- show_num_results (Optional[bool]): Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
-
- Returns:
- JournalistSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_JOURNALISTS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if twitter is not None:
- params["twitter"] = twitter
- if size is not None:
- params["size"] = size
- if page is not None:
- params["page"] = page
- if source is not None:
- params["source"] = source
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if label is not None:
- params["label"] = label
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if updated_at_from is not None:
- params["updatedAtFrom"] = updated_at_from
- if updated_at_to is not None:
- params["updatedAtTo"] = updated_at_to
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return JournalistSearchResult.model_validate(resp.json())
-
- # ----------------- search_journalists (async) ----------------- #
- async def search_journalists_async(
- self,
- id: Optional[List[str]] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- twitter: Optional[str] = None,
- size: Optional[int] = None,
- page: Optional[int] = None,
- source: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- updated_at_from: Optional[datetime] = None,
- updated_at_to: Optional[datetime] = None,
- show_num_results: Optional[bool] = None,
- ) -> JournalistSearchResult:
- """
- Async variant of search_journalists. Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
-
- Args:
- id (Optional[List[str]]): Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
- q (Optional[str]): Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- twitter (Optional[str]): Filter journalists by their exact Twitter handle, without the @ symbol.
- size (Optional[int]): The number of journalists to return per page in the paginated response.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- source (Optional[List[str]]): Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- topic (Optional[List[str]]): Filter journalists by the topics they frequently cover. Multiple values create an OR filter to find journalists covering any of the specified topics.
- category (Optional[List[str]]): Filter journalists by the content categories they typically write about (e.g., Politics, Tech, Sports, Business). Multiple values create an OR filter.
- label (Optional[List[str]]): Filter journalists by the type of content they typically produce (e.g., Opinion, Paid-news, Non-news). Multiple values create an OR filter.
- min_monthly_posts (Optional[int]): Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
- max_monthly_posts (Optional[int]): Filter for journalists who publish no more than this many articles per month.
- country (Optional[List[str]]): Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
- updated_at_from (Optional[datetime]): Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- updated_at_to (Optional[datetime]): Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
- show_num_results (Optional[bool]): Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
-
- Returns:
- JournalistSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_JOURNALISTS
-
- params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if twitter is not None:
- params["twitter"] = twitter
- if size is not None:
- params["size"] = size
- if page is not None:
- params["page"] = page
- if source is not None:
- params["source"] = source
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if label is not None:
- params["label"] = label
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if updated_at_from is not None:
- params["updatedAtFrom"] = updated_at_from
- if updated_at_to is not None:
- params["updatedAtTo"] = updated_at_to
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return JournalistSearchResult.model_validate(resp.json())
-
- # ----------------- search_people (sync) ----------------- #
- def search_people(
- self,
- name: Optional[str] = None,
- wikidata_id: Optional[List[str]] = None,
- occupation_id: Optional[List[str]] = None,
- occupation_label: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> PeopleSearchResult:
- """
- Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
-
- Args:
- name (Optional[str]): Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- wikidata_id (Optional[List[str]]): Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
- occupation_id (Optional[List[str]]): Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
- occupation_label (Optional[str]): Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of people to return per page in the paginated response.
-
- Returns:
- PeopleSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_PEOPLE
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if wikidata_id is not None:
- params["wikidataId"] = wikidata_id
- if occupation_id is not None:
- params["occupationId"] = occupation_id
- if occupation_label is not None:
- params["occupationLabel"] = occupation_label
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return PeopleSearchResult.model_validate(resp.json())
-
- # ----------------- search_people (async) ----------------- #
- async def search_people_async(
- self,
- name: Optional[str] = None,
- wikidata_id: Optional[List[str]] = None,
- occupation_id: Optional[List[str]] = None,
- occupation_label: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> PeopleSearchResult:
- """
- Async variant of search_people. Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
-
- Args:
- name (Optional[str]): Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- wikidata_id (Optional[List[str]]): Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
- occupation_id (Optional[List[str]]): Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
- occupation_label (Optional[str]): Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of people to return per page in the paginated response.
-
- Returns:
- PeopleSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_PEOPLE
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if wikidata_id is not None:
- params["wikidataId"] = wikidata_id
- if occupation_id is not None:
- params["occupationId"] = occupation_id
- if occupation_label is not None:
- params["occupationLabel"] = occupation_label
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return PeopleSearchResult.model_validate(resp.json())
-
- # ----------------- search_sources (sync) ----------------- #
- def search_sources(
- self,
- domain: Optional[List[str]] = None,
- name: Optional[str] = None,
- source_group: Optional[str] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- min_monthly_visits: Optional[int] = None,
- max_monthly_visits: Optional[int] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- source_country: Optional[List[str]] = None,
- source_state: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- source_city: Optional[List[str]] = None,
- source_lat: Optional[float] = None,
- source_lon: Optional[float] = None,
- source_max_distance: Optional[float] = None,
- category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- show_subdomains: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- ) -> SourceSearchResult:
- """
- Search and filter the 142,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
-
- Args:
- domain (Optional[List[str]]): Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
- name (Optional[str]): Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- source_group (Optional[str]): Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
- sort_by (Optional[SortBy]): Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of sources to return per page in the paginated response.
- min_monthly_visits (Optional[int]): Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
- max_monthly_visits (Optional[int]): Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
- min_monthly_posts (Optional[int]): Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
- max_monthly_posts (Optional[int]): Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
- country (Optional[List[str]]): Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_country (Optional[List[str]]): Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_state (Optional[List[str]]): Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
- source_county (Optional[List[str]]): Filter for local publications based in specific counties. Multiple values create an OR filter.
- source_city (Optional[List[str]]): Filter for local publications based in specific cities. Multiple values create an OR filter.
- source_lat (Optional[float]): Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
- source_lon (Optional[float]): Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
- source_max_distance (Optional[float]): Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
- category (Optional[List[str]]): Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
- topic (Optional[List[str]]): Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
- label (Optional[List[str]]): Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
- paywall (Optional[bool]): Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
- show_subdomains (Optional[bool]): Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
- show_num_results (Optional[bool]): Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
-
- Returns:
- SourceSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_SOURCES
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if domain is not None:
- params["domain"] = domain
- if name is not None:
- params["name"] = name
- if source_group is not None:
- params["sourceGroup"] = source_group
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if min_monthly_visits is not None:
- params["minMonthlyVisits"] = min_monthly_visits
- if max_monthly_visits is not None:
- params["maxMonthlyVisits"] = max_monthly_visits
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if source_country is not None:
- params["sourceCountry"] = source_country
- if source_state is not None:
- params["sourceState"] = source_state
- if source_county is not None:
- params["sourceCounty"] = source_county
- if source_city is not None:
- params["sourceCity"] = source_city
- if source_lat is not None:
- params["sourceLat"] = source_lat
- if source_lon is not None:
- params["sourceLon"] = source_lon
- if source_max_distance is not None:
- params["sourceMaxDistance"] = source_max_distance
- if category is not None:
- params["category"] = category
- if topic is not None:
- params["topic"] = topic
- if label is not None:
- params["label"] = label
- if paywall is not None:
- params["paywall"] = paywall
- if show_subdomains is not None:
- params["showSubdomains"] = show_subdomains
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return SourceSearchResult.model_validate(resp.json())
-
- # ----------------- search_sources (async) ----------------- #
- async def search_sources_async(
- self,
- domain: Optional[List[str]] = None,
- name: Optional[str] = None,
- source_group: Optional[str] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- min_monthly_visits: Optional[int] = None,
- max_monthly_visits: Optional[int] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- source_country: Optional[List[str]] = None,
- source_state: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- source_city: Optional[List[str]] = None,
- source_lat: Optional[float] = None,
- source_lon: Optional[float] = None,
- source_max_distance: Optional[float] = None,
- category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- show_subdomains: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- ) -> SourceSearchResult:
- """
- Async variant of search_sources. Search and filter the 142,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
-
- Args:
- domain (Optional[List[str]]): Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
- name (Optional[str]): Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- source_group (Optional[str]): Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
- sort_by (Optional[SortBy]): Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of sources to return per page in the paginated response.
- min_monthly_visits (Optional[int]): Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
- max_monthly_visits (Optional[int]): Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
- min_monthly_posts (Optional[int]): Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
- max_monthly_posts (Optional[int]): Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
- country (Optional[List[str]]): Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_country (Optional[List[str]]): Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_state (Optional[List[str]]): Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
- source_county (Optional[List[str]]): Filter for local publications based in specific counties. Multiple values create an OR filter.
- source_city (Optional[List[str]]): Filter for local publications based in specific cities. Multiple values create an OR filter.
- source_lat (Optional[float]): Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
- source_lon (Optional[float]): Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
- source_max_distance (Optional[float]): Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
- category (Optional[List[str]]): Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
- topic (Optional[List[str]]): Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
- label (Optional[List[str]]): Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
- paywall (Optional[bool]): Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
- show_subdomains (Optional[bool]): Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
- show_num_results (Optional[bool]): Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
-
- Returns:
- SourceSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_SOURCES
-
- params: Dict[str, Any] = {}
- if domain is not None:
- params["domain"] = domain
- if name is not None:
- params["name"] = name
- if source_group is not None:
- params["sourceGroup"] = source_group
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if min_monthly_visits is not None:
- params["minMonthlyVisits"] = min_monthly_visits
- if max_monthly_visits is not None:
- params["maxMonthlyVisits"] = max_monthly_visits
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if source_country is not None:
- params["sourceCountry"] = source_country
- if source_state is not None:
- params["sourceState"] = source_state
- if source_county is not None:
- params["sourceCounty"] = source_county
- if source_city is not None:
- params["sourceCity"] = source_city
- if source_lat is not None:
- params["sourceLat"] = source_lat
- if source_lon is not None:
- params["sourceLon"] = source_lon
- if source_max_distance is not None:
- params["sourceMaxDistance"] = source_max_distance
- if category is not None:
- params["category"] = category
- if topic is not None:
- params["topic"] = topic
- if label is not None:
- params["label"] = label
- if paywall is not None:
- params["paywall"] = paywall
- if show_subdomains is not None:
- params["showSubdomains"] = show_subdomains
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return SourceSearchResult.model_validate(resp.json())
-
- # ----------------- search_topics (sync) ----------------- #
- def search_topics(
- self,
- name: Optional[str] = None,
- category: Optional[str] = None,
- subcategory: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> TopicSearchResult:
- """
- Search through all available Topics that exist within the Perigon Database.
-
- Args:
- name (Optional[str]): Search for topics by exact name or partial text match. Does not support wildcards. Examples include Markets, Cryptocurrency, Climate Change, etc.
- category (Optional[str]): Filter topics by broad article categories such as Politics, Tech, Sports, Business, Finance, Entertainment, etc.
- subcategory (Optional[str]): Filter topics by their specific subcategory. Subcategories provide more granular classification beyond the main category, such as TV or Event.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of topics to return per page in the paginated response.
-
- Returns:
- TopicSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_TOPICS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if category is not None:
- params["category"] = category
- if subcategory is not None:
- params["subcategory"] = subcategory
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return TopicSearchResult.model_validate(resp.json())
-
- # ----------------- search_topics (async) ----------------- #
- async def search_topics_async(
- self,
- name: Optional[str] = None,
- category: Optional[str] = None,
- subcategory: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> TopicSearchResult:
- """
- Async variant of search_topics. Search through all available Topics that exist within the Perigon Database.
-
- Args:
- name (Optional[str]): Search for topics by exact name or partial text match. Does not support wildcards. Examples include Markets, Cryptocurrency, Climate Change, etc.
- category (Optional[str]): Filter topics by broad article categories such as Politics, Tech, Sports, Business, Finance, Entertainment, etc.
- subcategory (Optional[str]): Filter topics by their specific subcategory. Subcategories provide more granular classification beyond the main category, such as TV or Event.
- page (Optional[int]): The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): The number of topics to return per page in the paginated response.
-
- Returns:
- TopicSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_TOPICS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if category is not None:
- params["category"] = category
- if subcategory is not None:
- params["subcategory"] = subcategory
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return TopicSearchResult.model_validate(resp.json())
diff --git a/perigon/api/watchlists_api.py b/perigon/api/watchlists_api.py
deleted file mode 100644
index 84f37fe..0000000
--- a/perigon/api/watchlists_api.py
+++ /dev/null
@@ -1,389 +0,0 @@
-from enum import Enum
-from typing import Any, Dict, Iterable, List, Mapping, Optional
-
-from pydantic import Field, StrictInt, StrictStr
-from typing_extensions import Annotated
-
-from perigon.api_client import ApiClient
-from perigon.models.create_watchlist_params import CreateWatchlistParams
-from perigon.models.update_watchlist_params import UpdateWatchlistParams
-
-# Define API paths
-PATH_CREATE_WATCHLIST = "/v1/api/watchlists"
-PATH_DELETE_WATCHLIST = "/v1/api/watchlists/{id}"
-PATH_GET_WATCHLIST = "/v1/api/watchlists/{id}"
-PATH_LIST_WATCHLISTS = "/v1/api/watchlists"
-PATH_RESOLVE_WATCHLISTS = "/v1/api/watchlists/resolve"
-PATH_UPDATE_WATCHLIST = "/v1/api/watchlists/{id}"
-
-
-def _normalise_query(params: Mapping[str, Any]) -> Dict[str, Any]:
- """
- β’ Convert Enum β Enum.value
- β’ Convert list/tuple/set β CSV string (after Enum handling)
- β’ Skip None values
- """
- out: Dict[str, Any] = {}
- for key, value in params.items():
- if value is None: # ignore "unset"
- continue
-
- # Unwrap single Enum
- if isinstance(value, Enum): # Enum β str
- value = value.value
-
- # Handle datetime objects properly
- from datetime import datetime
-
- if isinstance(value, datetime):
- value = value.isoformat().split("+")[0]
-
- # Handle collection (after possible Enum unwrap)
- elif isinstance(value, (list, tuple, set)):
- # unwrap Enum members inside the collection
- items: Iterable[str] = (
- (
- item.isoformat().replace(" ", "+")
- if isinstance(item, datetime)
- else str(item.value if isinstance(item, Enum) else item)
- )
- for item in value
- )
- value = ",".join(items) # CSV join
- else:
- value = str(value)
-
- out[key] = value
-
- return out
-
-
-class WatchlistsApi:
- """"""
-
- def __init__(self, api_client: Optional[ApiClient] = None):
- self.api_client = api_client or ApiClient()
-
- # ----------------- create_watchlist (sync) ----------------- #
- def create_watchlist(self, create_watchlist_params: CreateWatchlistParams):
- """
- Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
-
- Args:
- create_watchlist_params (CreateWatchlistParams): Parameter create_watchlist_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_CREATE_WATCHLIST
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "POST",
- path,
- params=params,
- json=create_watchlist_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- create_watchlist (async) ----------------- #
- async def create_watchlist_async(
- self, create_watchlist_params: CreateWatchlistParams
- ):
- """
- Async variant of create_watchlist. Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
-
- Args:
- create_watchlist_params (CreateWatchlistParams): Parameter create_watchlist_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_CREATE_WATCHLIST
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async(
- "POST",
- path,
- params=params,
- json=create_watchlist_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- delete_watchlist (sync) ----------------- #
- def delete_watchlist(self, id: int):
- """
- Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active signals.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_DELETE_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request("DELETE", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- delete_watchlist (async) ----------------- #
- async def delete_watchlist_async(self, id: int):
- """
- Async variant of delete_watchlist. Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active signals.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_DELETE_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("DELETE", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- get_watchlist (sync) ----------------- #
- def get_watchlist(self, id: int):
- """
- Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_GET_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- get_watchlist (async) ----------------- #
- async def get_watchlist_async(self, id: int):
- """
- Async variant of get_watchlist. Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
-
- Args:
- id (int): Parameter id (required)
-
- """
- # Get path template from class attribute
- path = PATH_GET_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- list_watchlists (sync) ----------------- #
- def list_watchlists(
- self,
- sort_by: str,
- sort_order: str,
- name: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
- """
- List watchlists owned by the organization associated with the API key, as well as publicly visible watchlists. Supports filtering by name.
-
- Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Filter watchlists by name (case-insensitive, partial match)
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
-
- """
- # Get path template from class attribute
- path = PATH_LIST_WATCHLISTS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
- if sort_order is not None:
- params["sortOrder"] = sort_order
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- list_watchlists (async) ----------------- #
- async def list_watchlists_async(
- self,
- sort_by: str,
- sort_order: str,
- name: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
- """
- Async variant of list_watchlists. List watchlists owned by the organization associated with the API key, as well as publicly visible watchlists. Supports filtering by name.
-
- Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Filter watchlists by name (case-insensitive, partial match)
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
-
- """
- # Get path template from class attribute
- path = PATH_LIST_WATCHLISTS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
- if sort_order is not None:
- params["sortOrder"] = sort_order
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_watchlists (sync) ----------------- #
- def resolve_watchlists(self, name: Optional[List[str]] = None):
- """
- Resolve watchlists by name. For each name, returns the organization's private watchlist if one exists, otherwise falls back to the matching public watchlist.
-
- Args:
- name (Optional[List[str]]): Watchlist names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_WATCHLISTS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_watchlists (async) ----------------- #
- async def resolve_watchlists_async(self, name: Optional[List[str]] = None):
- """
- Async variant of resolve_watchlists. Resolve watchlists by name. For each name, returns the organization's private watchlist if one exists, otherwise falls back to the matching public watchlist.
-
- Args:
- name (Optional[List[str]]): Watchlist names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_WATCHLISTS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- update_watchlist (sync) ----------------- #
- def update_watchlist(self, id: int, update_watchlist_params: UpdateWatchlistParams):
- """
- Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
-
- Args:
- id (int): Parameter id (required)
- update_watchlist_params (UpdateWatchlistParams): Parameter update_watchlist_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_UPDATE_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "PATCH",
- path,
- params=params,
- json=update_watchlist_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- update_watchlist (async) ----------------- #
- async def update_watchlist_async(
- self, id: int, update_watchlist_params: UpdateWatchlistParams
- ):
- """
- Async variant of update_watchlist. Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
-
- Args:
- id (int): Parameter id (required)
- update_watchlist_params (UpdateWatchlistParams): Parameter update_watchlist_params (required)
-
- """
- # Get path template from class attribute
- path = PATH_UPDATE_WATCHLIST
-
- # Replace path parameters
- path = path.format(id=str(id))
-
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async(
- "PATCH",
- path,
- params=params,
- json=update_watchlist_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
diff --git a/perigon/api_client.py b/perigon/api_client.py
deleted file mode 100644
index ea631c8..0000000
--- a/perigon/api_client.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# PackageΒ : perigon
-from __future__ import annotations
-
-from typing import Any, Dict, Optional
-
-import httpx
-
-
-class ApiClient:
- """
- Single entryβpoint that wraps an httpx.Client *and* httpx.AsyncClient.
- Accepts api_key & base_url directly instead of a separate Configuration.
- """
-
- def __init__(
- self,
- api_key: Optional[str] = None,
- base_url: str = "https://api.perigon.io",
- timeout: Optional[float] = None,
- ):
- self.api_key = api_key
- self.base_url = base_url or "https://api.perigon.io"
- self.timeout = timeout
-
- # Persistent sessions for connectionβpool reuse (HTTP/1.1 or HTTP/2)
- self._sync = httpx.Client(base_url=self.base_url, timeout=self.timeout)
- self._async = httpx.AsyncClient(base_url=self.base_url, timeout=self.timeout)
-
- # ------------------------------------------------------------------ #
- # Internal helpers
- # ------------------------------------------------------------------ #
- def _auth_headers(self) -> Dict[str, str]:
- hdrs: Dict[str, str] = {}
-
- if self.api_key:
- hdrs["Authorization"] = f"Bearer {self.api_key}"
-
- return hdrs
-
- def _prepare_url(self, path: str) -> str:
- url = f"{self.base_url}{path}"
- return url
-
- # ------------------------------------------------------------------ #
- # Public request wrappers
- # ------------------------------------------------------------------ #
- def request(
- self,
- method: str,
- path: str,
- headers: Optional[Dict[str, str]] = None,
- **kwargs: Any,
- ) -> httpx.Response:
- all_headers = self._auth_headers()
- if headers: # merge userβprovided headers
- all_headers.update(headers)
- url = self._prepare_url(path)
- return self._sync.request(method, url, headers=all_headers, **kwargs)
-
- async def request_async(
- self,
- method: str,
- path: str,
- headers: Optional[Dict[str, str]] = None,
- **kwargs: Any,
- ) -> httpx.Response:
- all_headers = self._auth_headers()
- if headers:
- all_headers.update(headers)
- url = self._prepare_url(path)
- return await self._async.request(method, url, headers=all_headers, **kwargs)
-
- # ------------------------------------------------------------------ #
- # Cleanβup helpers
- # ------------------------------------------------------------------ #
- def close(self) -> None:
- self._sync.close()
-
- async def aclose(self) -> None:
- await self._async.aclose()
diff --git a/perigon/models/auth_exception.py b/perigon/models/auth_exception.py
deleted file mode 100644
index aaa08a3..0000000
--- a/perigon/models/auth_exception.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
-from typing_extensions import Self
-
-from perigon.models.auth_exception_cause import AuthExceptionCause
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
-from perigon.models.auth_exception_suppressed_inner import AuthExceptionSuppressedInner
-
-
-class AuthException(BaseModel):
- """
- AuthException
- """ # noqa: E501
-
- cause: Optional[AuthExceptionCause] = None
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- status_code: Optional[StrictStr] = Field(default=None, alias="statusCode")
- message: Optional[StrictStr] = None
- suppressed: Optional[List[AuthExceptionSuppressedInner]] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = [
- "cause",
- "stackTrace",
- "statusCode",
- "message",
- "suppressed",
- "localizedMessage",
- ]
-
- @field_validator("status_code")
- def status_code_validate_enum(cls, value):
- """Validates the enum"""
- if value is None:
- return value
-
- if value not in set(
- [
- "100 CONTINUE",
- "101 SWITCHING_PROTOCOLS",
- "102 PROCESSING",
- "103 CHECKPOINT",
- "200 OK",
- "201 CREATED",
- "202 ACCEPTED",
- "203 NON_AUTHORITATIVE_INFORMATION",
- "204 NO_CONTENT",
- "205 RESET_CONTENT",
- "206 PARTIAL_CONTENT",
- "207 MULTI_STATUS",
- "208 ALREADY_REPORTED",
- "226 IM_USED",
- "300 MULTIPLE_CHOICES",
- "301 MOVED_PERMANENTLY",
- "302 FOUND",
- "302 MOVED_TEMPORARILY",
- "303 SEE_OTHER",
- "304 NOT_MODIFIED",
- "305 USE_PROXY",
- "307 TEMPORARY_REDIRECT",
- "308 PERMANENT_REDIRECT",
- "400 BAD_REQUEST",
- "401 UNAUTHORIZED",
- "402 PAYMENT_REQUIRED",
- "403 FORBIDDEN",
- "404 NOT_FOUND",
- "405 METHOD_NOT_ALLOWED",
- "406 NOT_ACCEPTABLE",
- "407 PROXY_AUTHENTICATION_REQUIRED",
- "408 REQUEST_TIMEOUT",
- "409 CONFLICT",
- "410 GONE",
- "411 LENGTH_REQUIRED",
- "412 PRECONDITION_FAILED",
- "413 PAYLOAD_TOO_LARGE",
- "413 REQUEST_ENTITY_TOO_LARGE",
- "414 URI_TOO_LONG",
- "414 REQUEST_URI_TOO_LONG",
- "415 UNSUPPORTED_MEDIA_TYPE",
- "416 REQUESTED_RANGE_NOT_SATISFIABLE",
- "417 EXPECTATION_FAILED",
- "418 I_AM_A_TEAPOT",
- "419 INSUFFICIENT_SPACE_ON_RESOURCE",
- "420 METHOD_FAILURE",
- "421 DESTINATION_LOCKED",
- "422 UNPROCESSABLE_ENTITY",
- "423 LOCKED",
- "424 FAILED_DEPENDENCY",
- "425 TOO_EARLY",
- "426 UPGRADE_REQUIRED",
- "428 PRECONDITION_REQUIRED",
- "429 TOO_MANY_REQUESTS",
- "431 REQUEST_HEADER_FIELDS_TOO_LARGE",
- "451 UNAVAILABLE_FOR_LEGAL_REASONS",
- "500 INTERNAL_SERVER_ERROR",
- "501 NOT_IMPLEMENTED",
- "502 BAD_GATEWAY",
- "503 SERVICE_UNAVAILABLE",
- "504 GATEWAY_TIMEOUT",
- "505 HTTP_VERSION_NOT_SUPPORTED",
- "506 VARIANT_ALSO_NEGOTIATES",
- "507 INSUFFICIENT_STORAGE",
- "508 LOOP_DETECTED",
- "509 BANDWIDTH_LIMIT_EXCEEDED",
- "510 NOT_EXTENDED",
- "511 NETWORK_AUTHENTICATION_REQUIRED",
- ]
- ):
- raise ValueError(
- "must be one of enum values ('100 CONTINUE', '101 SWITCHING_PROTOCOLS', '102 PROCESSING', '103 CHECKPOINT', '200 OK', '201 CREATED', '202 ACCEPTED', '203 NON_AUTHORITATIVE_INFORMATION', '204 NO_CONTENT', '205 RESET_CONTENT', '206 PARTIAL_CONTENT', '207 MULTI_STATUS', '208 ALREADY_REPORTED', '226 IM_USED', '300 MULTIPLE_CHOICES', '301 MOVED_PERMANENTLY', '302 FOUND', '302 MOVED_TEMPORARILY', '303 SEE_OTHER', '304 NOT_MODIFIED', '305 USE_PROXY', '307 TEMPORARY_REDIRECT', '308 PERMANENT_REDIRECT', '400 BAD_REQUEST', '401 UNAUTHORIZED', '402 PAYMENT_REQUIRED', '403 FORBIDDEN', '404 NOT_FOUND', '405 METHOD_NOT_ALLOWED', '406 NOT_ACCEPTABLE', '407 PROXY_AUTHENTICATION_REQUIRED', '408 REQUEST_TIMEOUT', '409 CONFLICT', '410 GONE', '411 LENGTH_REQUIRED', '412 PRECONDITION_FAILED', '413 PAYLOAD_TOO_LARGE', '413 REQUEST_ENTITY_TOO_LARGE', '414 URI_TOO_LONG', '414 REQUEST_URI_TOO_LONG', '415 UNSUPPORTED_MEDIA_TYPE', '416 REQUESTED_RANGE_NOT_SATISFIABLE', '417 EXPECTATION_FAILED', '418 I_AM_A_TEAPOT', '419 INSUFFICIENT_SPACE_ON_RESOURCE', '420 METHOD_FAILURE', '421 DESTINATION_LOCKED', '422 UNPROCESSABLE_ENTITY', '423 LOCKED', '424 FAILED_DEPENDENCY', '425 TOO_EARLY', '426 UPGRADE_REQUIRED', '428 PRECONDITION_REQUIRED', '429 TOO_MANY_REQUESTS', '431 REQUEST_HEADER_FIELDS_TOO_LARGE', '451 UNAVAILABLE_FOR_LEGAL_REASONS', '500 INTERNAL_SERVER_ERROR', '501 NOT_IMPLEMENTED', '502 BAD_GATEWAY', '503 SERVICE_UNAVAILABLE', '504 GATEWAY_TIMEOUT', '505 HTTP_VERSION_NOT_SUPPORTED', '506 VARIANT_ALSO_NEGOTIATES', '507 INSUFFICIENT_STORAGE', '508 LOOP_DETECTED', '509 BANDWIDTH_LIMIT_EXCEEDED', '510 NOT_EXTENDED', '511 NETWORK_AUTHENTICATION_REQUIRED')"
- )
- return value
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of AuthException from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of cause
- if self.cause:
- _dict["cause"] = self.cause.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in suppressed (list)
- _items = []
- if self.suppressed:
- for _item_suppressed in self.suppressed:
- if _item_suppressed:
- _items.append(_item_suppressed.to_dict())
- _dict["suppressed"] = _items
- # set to None if cause (nullable) is None
- # and model_fields_set contains the field
- if self.cause is None and "cause" in self.model_fields_set:
- _dict["cause"] = None
-
- # set to None if stack_trace (nullable) is None
- # and model_fields_set contains the field
- if self.stack_trace is None and "stack_trace" in self.model_fields_set:
- _dict["stackTrace"] = None
-
- # set to None if status_code (nullable) is None
- # and model_fields_set contains the field
- if self.status_code is None and "status_code" in self.model_fields_set:
- _dict["statusCode"] = None
-
- # set to None if message (nullable) is None
- # and model_fields_set contains the field
- if self.message is None and "message" in self.model_fields_set:
- _dict["message"] = None
-
- # set to None if suppressed (nullable) is None
- # and model_fields_set contains the field
- if self.suppressed is None and "suppressed" in self.model_fields_set:
- _dict["suppressed"] = None
-
- # set to None if localized_message (nullable) is None
- # and model_fields_set contains the field
- if (
- self.localized_message is None
- and "localized_message" in self.model_fields_set
- ):
- _dict["localizedMessage"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of AuthException from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "cause": (
- AuthExceptionCause.from_dict(obj["cause"])
- if obj.get("cause") is not None
- else None
- ),
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "statusCode": obj.get("statusCode"),
- "message": obj.get("message"),
- "suppressed": (
- [
- AuthExceptionSuppressedInner.from_dict(_item)
- for _item in obj["suppressed"]
- ]
- if obj.get("suppressed") is not None
- else None
- ),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/perigon/models/auth_exception_cause_stack_trace_inner.py b/perigon/models/auth_exception_cause_stack_trace_inner.py
deleted file mode 100644
index a6b1034..0000000
--- a/perigon/models/auth_exception_cause_stack_trace_inner.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
-from typing_extensions import Self
-
-
-class AuthExceptionCauseStackTraceInner(BaseModel):
- """
- AuthExceptionCauseStackTraceInner
- """ # noqa: E501
-
- class_loader_name: Optional[StrictStr] = Field(
- default=None, alias="classLoaderName"
- )
- module_name: Optional[StrictStr] = Field(default=None, alias="moduleName")
- module_version: Optional[StrictStr] = Field(default=None, alias="moduleVersion")
- method_name: Optional[StrictStr] = Field(default=None, alias="methodName")
- file_name: Optional[StrictStr] = Field(default=None, alias="fileName")
- line_number: Optional[StrictInt] = Field(default=None, alias="lineNumber")
- native_method: Optional[StrictBool] = Field(default=None, alias="nativeMethod")
- class_name: Optional[StrictStr] = Field(default=None, alias="className")
- __properties: ClassVar[List[str]] = [
- "classLoaderName",
- "moduleName",
- "moduleVersion",
- "methodName",
- "fileName",
- "lineNumber",
- "nativeMethod",
- "className",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of AuthExceptionCauseStackTraceInner from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of AuthExceptionCauseStackTraceInner from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "classLoaderName": obj.get("classLoaderName"),
- "moduleName": obj.get("moduleName"),
- "moduleVersion": obj.get("moduleVersion"),
- "methodName": obj.get("methodName"),
- "fileName": obj.get("fileName"),
- "lineNumber": obj.get("lineNumber"),
- "nativeMethod": obj.get("nativeMethod"),
- "className": obj.get("className"),
- }
- )
- return _obj
diff --git a/perigon/models/illegal_parameter_exception.py b/perigon/models/illegal_parameter_exception.py
deleted file mode 100644
index eff900d..0000000
--- a/perigon/models/illegal_parameter_exception.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
-from typing_extensions import Self
-
-from perigon.models.auth_exception_cause import AuthExceptionCause
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
-from perigon.models.auth_exception_suppressed_inner import AuthExceptionSuppressedInner
-
-
-class IllegalParameterException(BaseModel):
- """
- IllegalParameterException
- """ # noqa: E501
-
- cause: Optional[AuthExceptionCause] = None
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- suppressed: Optional[List[AuthExceptionSuppressedInner]] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = [
- "cause",
- "stackTrace",
- "message",
- "suppressed",
- "localizedMessage",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of IllegalParameterException from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of cause
- if self.cause:
- _dict["cause"] = self.cause.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in suppressed (list)
- _items = []
- if self.suppressed:
- for _item_suppressed in self.suppressed:
- if _item_suppressed:
- _items.append(_item_suppressed.to_dict())
- _dict["suppressed"] = _items
- # set to None if cause (nullable) is None
- # and model_fields_set contains the field
- if self.cause is None and "cause" in self.model_fields_set:
- _dict["cause"] = None
-
- # set to None if stack_trace (nullable) is None
- # and model_fields_set contains the field
- if self.stack_trace is None and "stack_trace" in self.model_fields_set:
- _dict["stackTrace"] = None
-
- # set to None if message (nullable) is None
- # and model_fields_set contains the field
- if self.message is None and "message" in self.model_fields_set:
- _dict["message"] = None
-
- # set to None if suppressed (nullable) is None
- # and model_fields_set contains the field
- if self.suppressed is None and "suppressed" in self.model_fields_set:
- _dict["suppressed"] = None
-
- # set to None if localized_message (nullable) is None
- # and model_fields_set contains the field
- if (
- self.localized_message is None
- and "localized_message" in self.model_fields_set
- ):
- _dict["localizedMessage"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of IllegalParameterException from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "cause": (
- AuthExceptionCause.from_dict(obj["cause"])
- if obj.get("cause") is not None
- else None
- ),
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "suppressed": (
- [
- AuthExceptionSuppressedInner.from_dict(_item)
- for _item in obj["suppressed"]
- ]
- if obj.get("suppressed") is not None
- else None
- ),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/perigon/models/illegal_parameter_exception_cause_stack_trace_inner.py b/perigon/models/illegal_parameter_exception_cause_stack_trace_inner.py
deleted file mode 100644
index e4f3b67..0000000
--- a/perigon/models/illegal_parameter_exception_cause_stack_trace_inner.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
-from typing_extensions import Self
-
-
-class IllegalParameterExceptionCauseStackTraceInner(BaseModel):
- """
- IllegalParameterExceptionCauseStackTraceInner
- """ # noqa: E501
-
- class_loader_name: Optional[StrictStr] = Field(
- default=None, alias="classLoaderName"
- )
- module_name: Optional[StrictStr] = Field(default=None, alias="moduleName")
- module_version: Optional[StrictStr] = Field(default=None, alias="moduleVersion")
- method_name: Optional[StrictStr] = Field(default=None, alias="methodName")
- file_name: Optional[StrictStr] = Field(default=None, alias="fileName")
- line_number: Optional[StrictInt] = Field(default=None, alias="lineNumber")
- native_method: Optional[StrictBool] = Field(default=None, alias="nativeMethod")
- class_name: Optional[StrictStr] = Field(default=None, alias="className")
- __properties: ClassVar[List[str]] = [
- "classLoaderName",
- "moduleName",
- "moduleVersion",
- "methodName",
- "fileName",
- "lineNumber",
- "nativeMethod",
- "className",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionCauseStackTraceInner from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionCauseStackTraceInner from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "classLoaderName": obj.get("classLoaderName"),
- "moduleName": obj.get("moduleName"),
- "moduleVersion": obj.get("moduleVersion"),
- "methodName": obj.get("methodName"),
- "fileName": obj.get("fileName"),
- "lineNumber": obj.get("lineNumber"),
- "nativeMethod": obj.get("nativeMethod"),
- "className": obj.get("className"),
- }
- )
- return _obj
diff --git a/perigon/models/internal_error_exception.py b/perigon/models/internal_error_exception.py
deleted file mode 100644
index adda545..0000000
--- a/perigon/models/internal_error_exception.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
-from typing_extensions import Self
-
-from perigon.models.auth_exception_cause import AuthExceptionCause
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
-from perigon.models.auth_exception_suppressed_inner import AuthExceptionSuppressedInner
-
-
-class InternalErrorException(BaseModel):
- """
- InternalErrorException
- """ # noqa: E501
-
- cause: Optional[AuthExceptionCause] = None
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- suppressed: Optional[List[AuthExceptionSuppressedInner]] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = [
- "cause",
- "stackTrace",
- "message",
- "suppressed",
- "localizedMessage",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of InternalErrorException from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of cause
- if self.cause:
- _dict["cause"] = self.cause.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in suppressed (list)
- _items = []
- if self.suppressed:
- for _item_suppressed in self.suppressed:
- if _item_suppressed:
- _items.append(_item_suppressed.to_dict())
- _dict["suppressed"] = _items
- # set to None if cause (nullable) is None
- # and model_fields_set contains the field
- if self.cause is None and "cause" in self.model_fields_set:
- _dict["cause"] = None
-
- # set to None if stack_trace (nullable) is None
- # and model_fields_set contains the field
- if self.stack_trace is None and "stack_trace" in self.model_fields_set:
- _dict["stackTrace"] = None
-
- # set to None if message (nullable) is None
- # and model_fields_set contains the field
- if self.message is None and "message" in self.model_fields_set:
- _dict["message"] = None
-
- # set to None if suppressed (nullable) is None
- # and model_fields_set contains the field
- if self.suppressed is None and "suppressed" in self.model_fields_set:
- _dict["suppressed"] = None
-
- # set to None if localized_message (nullable) is None
- # and model_fields_set contains the field
- if (
- self.localized_message is None
- and "localized_message" in self.model_fields_set
- ):
- _dict["localizedMessage"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of InternalErrorException from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "cause": (
- AuthExceptionCause.from_dict(obj["cause"])
- if obj.get("cause") is not None
- else None
- ),
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "suppressed": (
- [
- AuthExceptionSuppressedInner.from_dict(_item)
- for _item in obj["suppressed"]
- ]
- if obj.get("suppressed") is not None
- else None
- ),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/perigon/models/not_found_exception.py b/perigon/models/not_found_exception.py
deleted file mode 100644
index 1d89987..0000000
--- a/perigon/models/not_found_exception.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
-from typing_extensions import Self
-
-from perigon.models.auth_exception_cause import AuthExceptionCause
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
-from perigon.models.auth_exception_suppressed_inner import AuthExceptionSuppressedInner
-
-
-class NotFoundException(BaseModel):
- """
- NotFoundException
- """ # noqa: E501
-
- cause: Optional[AuthExceptionCause] = None
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- suppressed: Optional[List[AuthExceptionSuppressedInner]] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = [
- "cause",
- "stackTrace",
- "message",
- "suppressed",
- "localizedMessage",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of NotFoundException from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of cause
- if self.cause:
- _dict["cause"] = self.cause.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in suppressed (list)
- _items = []
- if self.suppressed:
- for _item_suppressed in self.suppressed:
- if _item_suppressed:
- _items.append(_item_suppressed.to_dict())
- _dict["suppressed"] = _items
- # set to None if cause (nullable) is None
- # and model_fields_set contains the field
- if self.cause is None and "cause" in self.model_fields_set:
- _dict["cause"] = None
-
- # set to None if stack_trace (nullable) is None
- # and model_fields_set contains the field
- if self.stack_trace is None and "stack_trace" in self.model_fields_set:
- _dict["stackTrace"] = None
-
- # set to None if message (nullable) is None
- # and model_fields_set contains the field
- if self.message is None and "message" in self.model_fields_set:
- _dict["message"] = None
-
- # set to None if suppressed (nullable) is None
- # and model_fields_set contains the field
- if self.suppressed is None and "suppressed" in self.model_fields_set:
- _dict["suppressed"] = None
-
- # set to None if localized_message (nullable) is None
- # and model_fields_set contains the field
- if (
- self.localized_message is None
- and "localized_message" in self.model_fields_set
- ):
- _dict["localizedMessage"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of NotFoundException from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "cause": (
- AuthExceptionCause.from_dict(obj["cause"])
- if obj.get("cause") is not None
- else None
- ),
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "suppressed": (
- [
- AuthExceptionSuppressedInner.from_dict(_item)
- for _item in obj["suppressed"]
- ]
- if obj.get("suppressed") is not None
- else None
- ),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/perigon/models/scored_data_article.py b/perigon/models/scored_data_article.py
deleted file mode 100644
index 71a7852..0000000
--- a/perigon/models/scored_data_article.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
-from typing_extensions import Self
-
-from perigon.models.article import Article
-from perigon.models.vector_data import VectorData
-
-
-class ScoredDataArticle(BaseModel):
- """
- ScoredDataArticle
- """ # noqa: E501
-
- score: Optional[Union[StrictFloat, StrictInt]] = None
- data: Optional[Article] = None
- vectors: Optional[List[VectorData]] = None
- __properties: ClassVar[List[str]] = ["score", "data", "vectors"]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of ScoredDataArticle from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of data
- if self.data:
- _dict["data"] = self.data.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in vectors (list)
- _items = []
- if self.vectors:
- for _item_vectors in self.vectors:
- if _item_vectors:
- _items.append(_item_vectors.to_dict())
- _dict["vectors"] = _items
- # set to None if score (nullable) is None
- # and model_fields_set contains the field
- if self.score is None and "score" in self.model_fields_set:
- _dict["score"] = None
-
- # set to None if data (nullable) is None
- # and model_fields_set contains the field
- if self.data is None and "data" in self.model_fields_set:
- _dict["data"] = None
-
- # set to None if vectors (nullable) is None
- # and model_fields_set contains the field
- if self.vectors is None and "vectors" in self.model_fields_set:
- _dict["vectors"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of ScoredDataArticle from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "score": obj.get("score"),
- "data": (
- Article.from_dict(obj["data"])
- if obj.get("data") is not None
- else None
- ),
- "vectors": (
- [VectorData.from_dict(_item) for _item in obj["vectors"]]
- if obj.get("vectors") is not None
- else None
- ),
- }
- )
- return _obj
diff --git a/perigon/models/too_many_requests_exception.py b/perigon/models/too_many_requests_exception.py
deleted file mode 100644
index f0cb994..0000000
--- a/perigon/models/too_many_requests_exception.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
-from typing_extensions import Self
-
-from perigon.models.auth_exception_cause import AuthExceptionCause
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
-from perigon.models.auth_exception_suppressed_inner import AuthExceptionSuppressedInner
-
-
-class TooManyRequestsException(BaseModel):
- """
- TooManyRequestsException
- """ # noqa: E501
-
- cause: Optional[AuthExceptionCause] = None
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- status: Optional[StrictStr] = None
- message: Optional[StrictStr] = None
- suppressed: Optional[List[AuthExceptionSuppressedInner]] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = [
- "cause",
- "stackTrace",
- "status",
- "message",
- "suppressed",
- "localizedMessage",
- ]
-
- @field_validator("status")
- def status_validate_enum(cls, value):
- """Validates the enum"""
- if value is None:
- return value
-
- if value not in set(
- [
- "100 CONTINUE",
- "101 SWITCHING_PROTOCOLS",
- "102 PROCESSING",
- "103 CHECKPOINT",
- "200 OK",
- "201 CREATED",
- "202 ACCEPTED",
- "203 NON_AUTHORITATIVE_INFORMATION",
- "204 NO_CONTENT",
- "205 RESET_CONTENT",
- "206 PARTIAL_CONTENT",
- "207 MULTI_STATUS",
- "208 ALREADY_REPORTED",
- "226 IM_USED",
- "300 MULTIPLE_CHOICES",
- "301 MOVED_PERMANENTLY",
- "302 FOUND",
- "302 MOVED_TEMPORARILY",
- "303 SEE_OTHER",
- "304 NOT_MODIFIED",
- "305 USE_PROXY",
- "307 TEMPORARY_REDIRECT",
- "308 PERMANENT_REDIRECT",
- "400 BAD_REQUEST",
- "401 UNAUTHORIZED",
- "402 PAYMENT_REQUIRED",
- "403 FORBIDDEN",
- "404 NOT_FOUND",
- "405 METHOD_NOT_ALLOWED",
- "406 NOT_ACCEPTABLE",
- "407 PROXY_AUTHENTICATION_REQUIRED",
- "408 REQUEST_TIMEOUT",
- "409 CONFLICT",
- "410 GONE",
- "411 LENGTH_REQUIRED",
- "412 PRECONDITION_FAILED",
- "413 PAYLOAD_TOO_LARGE",
- "413 REQUEST_ENTITY_TOO_LARGE",
- "414 URI_TOO_LONG",
- "414 REQUEST_URI_TOO_LONG",
- "415 UNSUPPORTED_MEDIA_TYPE",
- "416 REQUESTED_RANGE_NOT_SATISFIABLE",
- "417 EXPECTATION_FAILED",
- "418 I_AM_A_TEAPOT",
- "419 INSUFFICIENT_SPACE_ON_RESOURCE",
- "420 METHOD_FAILURE",
- "421 DESTINATION_LOCKED",
- "422 UNPROCESSABLE_ENTITY",
- "423 LOCKED",
- "424 FAILED_DEPENDENCY",
- "425 TOO_EARLY",
- "426 UPGRADE_REQUIRED",
- "428 PRECONDITION_REQUIRED",
- "429 TOO_MANY_REQUESTS",
- "431 REQUEST_HEADER_FIELDS_TOO_LARGE",
- "451 UNAVAILABLE_FOR_LEGAL_REASONS",
- "500 INTERNAL_SERVER_ERROR",
- "501 NOT_IMPLEMENTED",
- "502 BAD_GATEWAY",
- "503 SERVICE_UNAVAILABLE",
- "504 GATEWAY_TIMEOUT",
- "505 HTTP_VERSION_NOT_SUPPORTED",
- "506 VARIANT_ALSO_NEGOTIATES",
- "507 INSUFFICIENT_STORAGE",
- "508 LOOP_DETECTED",
- "509 BANDWIDTH_LIMIT_EXCEEDED",
- "510 NOT_EXTENDED",
- "511 NETWORK_AUTHENTICATION_REQUIRED",
- ]
- ):
- raise ValueError(
- "must be one of enum values ('100 CONTINUE', '101 SWITCHING_PROTOCOLS', '102 PROCESSING', '103 CHECKPOINT', '200 OK', '201 CREATED', '202 ACCEPTED', '203 NON_AUTHORITATIVE_INFORMATION', '204 NO_CONTENT', '205 RESET_CONTENT', '206 PARTIAL_CONTENT', '207 MULTI_STATUS', '208 ALREADY_REPORTED', '226 IM_USED', '300 MULTIPLE_CHOICES', '301 MOVED_PERMANENTLY', '302 FOUND', '302 MOVED_TEMPORARILY', '303 SEE_OTHER', '304 NOT_MODIFIED', '305 USE_PROXY', '307 TEMPORARY_REDIRECT', '308 PERMANENT_REDIRECT', '400 BAD_REQUEST', '401 UNAUTHORIZED', '402 PAYMENT_REQUIRED', '403 FORBIDDEN', '404 NOT_FOUND', '405 METHOD_NOT_ALLOWED', '406 NOT_ACCEPTABLE', '407 PROXY_AUTHENTICATION_REQUIRED', '408 REQUEST_TIMEOUT', '409 CONFLICT', '410 GONE', '411 LENGTH_REQUIRED', '412 PRECONDITION_FAILED', '413 PAYLOAD_TOO_LARGE', '413 REQUEST_ENTITY_TOO_LARGE', '414 URI_TOO_LONG', '414 REQUEST_URI_TOO_LONG', '415 UNSUPPORTED_MEDIA_TYPE', '416 REQUESTED_RANGE_NOT_SATISFIABLE', '417 EXPECTATION_FAILED', '418 I_AM_A_TEAPOT', '419 INSUFFICIENT_SPACE_ON_RESOURCE', '420 METHOD_FAILURE', '421 DESTINATION_LOCKED', '422 UNPROCESSABLE_ENTITY', '423 LOCKED', '424 FAILED_DEPENDENCY', '425 TOO_EARLY', '426 UPGRADE_REQUIRED', '428 PRECONDITION_REQUIRED', '429 TOO_MANY_REQUESTS', '431 REQUEST_HEADER_FIELDS_TOO_LARGE', '451 UNAVAILABLE_FOR_LEGAL_REASONS', '500 INTERNAL_SERVER_ERROR', '501 NOT_IMPLEMENTED', '502 BAD_GATEWAY', '503 SERVICE_UNAVAILABLE', '504 GATEWAY_TIMEOUT', '505 HTTP_VERSION_NOT_SUPPORTED', '506 VARIANT_ALSO_NEGOTIATES', '507 INSUFFICIENT_STORAGE', '508 LOOP_DETECTED', '509 BANDWIDTH_LIMIT_EXCEEDED', '510 NOT_EXTENDED', '511 NETWORK_AUTHENTICATION_REQUIRED')"
- )
- return value
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of TooManyRequestsException from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of cause
- if self.cause:
- _dict["cause"] = self.cause.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in suppressed (list)
- _items = []
- if self.suppressed:
- for _item_suppressed in self.suppressed:
- if _item_suppressed:
- _items.append(_item_suppressed.to_dict())
- _dict["suppressed"] = _items
- # set to None if cause (nullable) is None
- # and model_fields_set contains the field
- if self.cause is None and "cause" in self.model_fields_set:
- _dict["cause"] = None
-
- # set to None if stack_trace (nullable) is None
- # and model_fields_set contains the field
- if self.stack_trace is None and "stack_trace" in self.model_fields_set:
- _dict["stackTrace"] = None
-
- # set to None if status (nullable) is None
- # and model_fields_set contains the field
- if self.status is None and "status" in self.model_fields_set:
- _dict["status"] = None
-
- # set to None if message (nullable) is None
- # and model_fields_set contains the field
- if self.message is None and "message" in self.model_fields_set:
- _dict["message"] = None
-
- # set to None if suppressed (nullable) is None
- # and model_fields_set contains the field
- if self.suppressed is None and "suppressed" in self.model_fields_set:
- _dict["suppressed"] = None
-
- # set to None if localized_message (nullable) is None
- # and model_fields_set contains the field
- if (
- self.localized_message is None
- and "localized_message" in self.model_fields_set
- ):
- _dict["localizedMessage"] = None
-
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of TooManyRequestsException from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "cause": (
- AuthExceptionCause.from_dict(obj["cause"])
- if obj.get("cause") is not None
- else None
- ),
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "status": obj.get("status"),
- "message": obj.get("message"),
- "suppressed": (
- [
- AuthExceptionSuppressedInner.from_dict(_item)
- for _item in obj["suppressed"]
- ]
- if obj.get("suppressed") is not None
- else None
- ),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/perigon/models/too_many_requests_exception_cause_stack_trace_inner.py b/perigon/models/too_many_requests_exception_cause_stack_trace_inner.py
deleted file mode 100644
index 60c0eb1..0000000
--- a/perigon/models/too_many_requests_exception_cause_stack_trace_inner.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
-from typing_extensions import Self
-
-
-class TooManyRequestsExceptionCauseStackTraceInner(BaseModel):
- """
- TooManyRequestsExceptionCauseStackTraceInner
- """ # noqa: E501
-
- class_loader_name: Optional[StrictStr] = Field(
- default=None, alias="classLoaderName"
- )
- module_name: Optional[StrictStr] = Field(default=None, alias="moduleName")
- module_version: Optional[StrictStr] = Field(default=None, alias="moduleVersion")
- method_name: Optional[StrictStr] = Field(default=None, alias="methodName")
- file_name: Optional[StrictStr] = Field(default=None, alias="fileName")
- line_number: Optional[StrictInt] = Field(default=None, alias="lineNumber")
- native_method: Optional[StrictBool] = Field(default=None, alias="nativeMethod")
- class_name: Optional[StrictStr] = Field(default=None, alias="className")
- __properties: ClassVar[List[str]] = [
- "classLoaderName",
- "moduleName",
- "moduleVersion",
- "methodName",
- "fileName",
- "lineNumber",
- "nativeMethod",
- "className",
- ]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionCauseStackTraceInner from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionCauseStackTraceInner from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "classLoaderName": obj.get("classLoaderName"),
- "moduleName": obj.get("moduleName"),
- "moduleVersion": obj.get("moduleVersion"),
- "methodName": obj.get("methodName"),
- "fileName": obj.get("fileName"),
- "lineNumber": obj.get("lineNumber"),
- "nativeMethod": obj.get("nativeMethod"),
- "className": obj.get("className"),
- }
- )
- return _obj
diff --git a/perigon/models/too_many_requests_exception_suppressed_inner.py b/perigon/models/too_many_requests_exception_suppressed_inner.py
deleted file mode 100644
index 3e51aa3..0000000
--- a/perigon/models/too_many_requests_exception_suppressed_inner.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# coding: utf-8
-
-"""
-Perigon API
-
-The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
-
-The version of the OpenAPI document: 1.0.0
-Contact: data@perigon.io
-Generated by OpenAPI Generator (https://openapi-generator.tech)
-
-Do not edit the class manually.
-""" # noqa: E501
-
-
-from __future__ import annotations
-
-import json
-import pprint
-import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set
-
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
-from typing_extensions import Self
-
-from perigon.models.too_many_requests_exception_cause_stack_trace_inner import (
- TooManyRequestsExceptionCauseStackTraceInner,
-)
-
-
-class TooManyRequestsExceptionSuppressedInner(BaseModel):
- """
- TooManyRequestsExceptionSuppressedInner
- """ # noqa: E501
-
- stack_trace: Optional[List[TooManyRequestsExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
-
- model_config = ConfigDict(
- populate_by_name=True,
- validate_assignment=True,
- protected_namespaces=(),
- )
-
- def to_str(self) -> str:
- """Returns the string representation of the model using alias"""
- return pprint.pformat(self.model_dump(by_alias=True))
-
- def to_json(self) -> str:
- """Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
-
- @classmethod
- def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionSuppressedInner from a JSON string"""
- return cls.from_dict(json.loads(json_str))
-
- def to_dict(self) -> Dict[str, Any]:
- """Return the dictionary representation of the model using alias.
-
- This has the following differences from calling pydantic's
- `self.model_dump(by_alias=True)`:
-
- * `None` is only added to the output dict for nullable fields that
- were set at model initialization. Other fields with value `None`
- are ignored.
- """
- excluded_fields: Set[str] = set([])
-
- _dict = self.model_dump(
- by_alias=True,
- exclude=excluded_fields,
- exclude_none=True,
- )
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
- return _dict
-
- @classmethod
- def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionSuppressedInner from a dict"""
- if obj is None:
- return None
-
- if not isinstance(obj, dict):
- return cls.model_validate(obj)
-
- _obj = cls.model_validate(
- {
- "stackTrace": (
- [
- TooManyRequestsExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
- }
- )
- return _obj
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index b1e55b4..0000000
--- a/poetry.lock
+++ /dev/null
@@ -1,919 +0,0 @@
-# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
-
-[[package]]
-name = "annotated-types"
-version = "0.7.0"
-description = "Reusable constraint types to use with typing.Annotated"
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
- {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
-]
-
-[[package]]
-name = "anyio"
-version = "4.9.0"
-description = "High level compatibility layer for multiple asynchronous event loop implementations"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
- {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
-]
-
-[package.dependencies]
-exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
-idna = ">=2.8"
-sniffio = ">=1.1"
-typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
-
-[package.extras]
-doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
-test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
-trio = ["trio (>=0.26.1)"]
-
-[[package]]
-name = "black"
-version = "25.1.0"
-description = "The uncompromising code formatter."
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
- {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
- {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
- {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
- {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
- {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
- {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
- {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
- {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
- {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
- {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
- {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
- {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
- {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
- {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
- {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
- {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
- {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
- {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
- {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
- {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
- {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
-]
-
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-packaging = ">=22.0"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.10)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
-[[package]]
-name = "cachetools"
-version = "5.5.2"
-description = "Extensible memoizing collections and decorators"
-optional = false
-python-versions = ">=3.7"
-groups = ["dev"]
-files = [
- {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"},
- {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"},
-]
-
-[[package]]
-name = "certifi"
-version = "2025.4.26"
-description = "Python package for providing Mozilla's CA Bundle."
-optional = false
-python-versions = ">=3.6"
-groups = ["main"]
-files = [
- {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
- {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
-]
-
-[[package]]
-name = "chardet"
-version = "5.2.0"
-description = "Universal encoding detector for Python 3"
-optional = false
-python-versions = ">=3.7"
-groups = ["dev"]
-files = [
- {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
- {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
-]
-
-[[package]]
-name = "click"
-version = "8.1.8"
-description = "Composable command line interface toolkit"
-optional = false
-python-versions = ">=3.7"
-groups = ["dev"]
-files = [
- {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
- {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
-]
-
-[package.dependencies]
-colorama = {version = "*", markers = "platform_system == \"Windows\""}
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-description = "Cross-platform colored terminal text."
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-groups = ["dev"]
-files = [
- {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
- {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
-]
-
-[[package]]
-name = "coverage"
-version = "7.8.0"
-description = "Code coverage measurement for Python"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"},
- {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"},
- {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"},
- {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"},
- {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"},
- {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"},
- {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"},
- {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"},
- {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"},
- {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"},
- {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"},
- {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"},
- {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"},
- {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"},
- {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"},
- {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"},
- {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"},
- {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"},
- {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"},
- {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"},
- {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"},
- {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"},
- {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"},
- {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"},
- {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"},
- {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"},
- {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"},
- {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"},
- {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"},
- {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"},
- {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"},
- {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"},
- {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"},
- {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"},
- {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"},
- {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"},
- {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"},
- {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"},
- {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"},
- {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"},
- {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"},
- {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"},
- {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"},
- {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"},
- {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"},
- {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"},
- {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"},
- {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"},
- {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"},
- {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"},
- {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"},
- {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"},
- {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"},
- {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"},
- {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"},
- {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"},
- {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"},
- {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"},
- {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"},
- {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"},
- {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"},
- {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"},
- {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"},
-]
-
-[package.dependencies]
-tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
-
-[package.extras]
-toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
-
-[[package]]
-name = "distlib"
-version = "0.3.9"
-description = "Distribution utilities"
-optional = false
-python-versions = "*"
-groups = ["dev"]
-files = [
- {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
- {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
-]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.2.2"
-description = "Backport of PEP 654 (exception groups)"
-optional = false
-python-versions = ">=3.7"
-groups = ["main", "dev"]
-markers = "python_version < \"3.11\""
-files = [
- {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
- {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
-name = "filelock"
-version = "3.18.0"
-description = "A platform independent file lock."
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
- {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
-]
-
-[package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
-typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
-
-[[package]]
-name = "flake8"
-version = "7.2.0"
-description = "the modular source code checker: pep8 pyflakes and co"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"},
- {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"},
-]
-
-[package.dependencies]
-mccabe = ">=0.7.0,<0.8.0"
-pycodestyle = ">=2.13.0,<2.14.0"
-pyflakes = ">=3.3.0,<3.4.0"
-
-[[package]]
-name = "h11"
-version = "0.16.0"
-description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
- {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
-]
-
-[[package]]
-name = "httpcore"
-version = "1.0.9"
-description = "A minimal low-level HTTP client."
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"},
- {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"},
-]
-
-[package.dependencies]
-certifi = "*"
-h11 = ">=0.16"
-
-[package.extras]
-asyncio = ["anyio (>=4.0,<5.0)"]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (==1.*)"]
-trio = ["trio (>=0.22.0,<1.0)"]
-
-[[package]]
-name = "httpx"
-version = "0.28.1"
-description = "The next generation HTTP client."
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
- {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
-]
-
-[package.dependencies]
-anyio = "*"
-certifi = "*"
-httpcore = "==1.*"
-idna = "*"
-
-[package.extras]
-brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
-cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (==1.*)"]
-zstd = ["zstandard (>=0.18.0)"]
-
-[[package]]
-name = "idna"
-version = "3.10"
-description = "Internationalized Domain Names in Applications (IDNA)"
-optional = false
-python-versions = ">=3.6"
-groups = ["main"]
-files = [
- {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
- {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
-]
-
-[package.extras]
-all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
-
-[[package]]
-name = "iniconfig"
-version = "2.1.0"
-description = "brain-dead simple config-ini parsing"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
- {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
-]
-
-[[package]]
-name = "isort"
-version = "6.0.1"
-description = "A Python utility / library to sort Python imports."
-optional = false
-python-versions = ">=3.9.0"
-groups = ["dev"]
-files = [
- {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
- {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
-]
-
-[package.extras]
-colors = ["colorama"]
-plugins = ["setuptools"]
-
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-description = "McCabe checker, plugin for flake8"
-optional = false
-python-versions = ">=3.6"
-groups = ["dev"]
-files = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-
-[[package]]
-name = "mypy"
-version = "1.15.0"
-description = "Optional static typing for Python"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
- {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
- {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
- {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
- {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
- {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
- {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
- {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
- {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
- {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
- {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
- {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
- {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
- {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
- {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
- {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
- {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
- {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
- {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
- {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
- {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
- {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
- {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
- {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
- {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
- {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
- {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
- {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
- {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
- {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
- {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
- {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
-]
-
-[package.dependencies]
-mypy_extensions = ">=1.0.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing_extensions = ">=4.6.0"
-
-[package.extras]
-dmypy = ["psutil (>=4.0)"]
-faster-cache = ["orjson"]
-install-types = ["pip"]
-mypyc = ["setuptools (>=50)"]
-reports = ["lxml"]
-
-[[package]]
-name = "mypy-extensions"
-version = "1.1.0"
-description = "Type system extensions for programs checked with the mypy type checker."
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
- {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
-]
-
-[[package]]
-name = "packaging"
-version = "25.0"
-description = "Core utilities for Python packages"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
- {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
-]
-
-[[package]]
-name = "pathspec"
-version = "0.12.1"
-description = "Utility library for gitignore style pattern matching of file paths."
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
- {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
-]
-
-[[package]]
-name = "platformdirs"
-version = "4.3.7"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"},
- {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"},
-]
-
-[package.extras]
-docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
-type = ["mypy (>=1.14.1)"]
-
-[[package]]
-name = "pluggy"
-version = "1.5.0"
-description = "plugin and hook calling mechanisms for python"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
- {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
-]
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
-[[package]]
-name = "pycodestyle"
-version = "2.13.0"
-description = "Python style guide checker"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"},
- {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"},
-]
-
-[[package]]
-name = "pydantic"
-version = "2.11.4"
-description = "Data validation using Python type hints"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb"},
- {file = "pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d"},
-]
-
-[package.dependencies]
-annotated-types = ">=0.6.0"
-pydantic-core = "2.33.2"
-typing-extensions = ">=4.12.2"
-typing-inspection = ">=0.4.0"
-
-[package.extras]
-email = ["email-validator (>=2.0.0)"]
-timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
-
-[[package]]
-name = "pydantic-core"
-version = "2.33.2"
-description = "Core functionality for Pydantic validation and serialization"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
- {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
- {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
- {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
- {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
- {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
- {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
- {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
- {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
- {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
- {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
- {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
- {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
-]
-
-[package.dependencies]
-typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
-
-[[package]]
-name = "pyflakes"
-version = "3.3.2"
-description = "passive checker of Python programs"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"},
- {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"},
-]
-
-[[package]]
-name = "pyproject-api"
-version = "1.9.0"
-description = "API to interact with the python pyproject.toml based projects"
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766"},
- {file = "pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e"},
-]
-
-[package.dependencies]
-packaging = ">=24.2"
-tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
-
-[package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3)"]
-testing = ["covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "setuptools (>=75.8)"]
-
-[[package]]
-name = "pytest"
-version = "8.3.5"
-description = "pytest: simple powerful testing with Python"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
- {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
-]
-
-[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=1.5,<2"
-tomli = {version = ">=1", markers = "python_version < \"3.11\""}
-
-[package.extras]
-dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
-
-[[package]]
-name = "pytest-cov"
-version = "6.1.1"
-description = "Pytest plugin for measuring coverage."
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"},
- {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"},
-]
-
-[package.dependencies]
-coverage = {version = ">=7.5", extras = ["toml"]}
-pytest = ">=4.6"
-
-[package.extras]
-testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
-
-[[package]]
-name = "python-dateutil"
-version = "2.9.0.post0"
-description = "Extensions to the standard Python datetime module"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-groups = ["main"]
-files = [
- {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
- {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
-]
-
-[package.dependencies]
-six = ">=1.5"
-
-[[package]]
-name = "six"
-version = "1.17.0"
-description = "Python 2 and 3 compatibility utilities"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-groups = ["main"]
-files = [
- {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
- {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
-]
-
-[[package]]
-name = "sniffio"
-version = "1.3.1"
-description = "Sniff out which async library your code is running under"
-optional = false
-python-versions = ">=3.7"
-groups = ["main"]
-files = [
- {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
- {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
-]
-
-[[package]]
-name = "tomli"
-version = "2.2.1"
-description = "A lil' TOML parser"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-markers = "python_full_version <= \"3.11.0a6\""
-files = [
- {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
- {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
- {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
- {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
- {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
- {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
- {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
- {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
- {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
- {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
-]
-
-[[package]]
-name = "tox"
-version = "4.25.0"
-description = "tox is a generic virtualenv management and test command line tool"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c"},
- {file = "tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52"},
-]
-
-[package.dependencies]
-cachetools = ">=5.5.1"
-chardet = ">=5.2"
-colorama = ">=0.4.6"
-filelock = ">=3.16.1"
-packaging = ">=24.2"
-platformdirs = ">=4.3.6"
-pluggy = ">=1.5"
-pyproject-api = ">=1.8"
-tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""}
-virtualenv = ">=20.29.1"
-
-[package.extras]
-test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"]
-
-[[package]]
-name = "types-python-dateutil"
-version = "2.9.0.20241206"
-description = "Typing stubs for python-dateutil"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"},
- {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"},
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.13.2"
-description = "Backported and Experimental Type Hints for Python 3.8+"
-optional = false
-python-versions = ">=3.8"
-groups = ["main", "dev"]
-files = [
- {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
- {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
-]
-
-[[package]]
-name = "typing-inspection"
-version = "0.4.0"
-description = "Runtime typing introspection tools"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
- {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
-]
-
-[package.dependencies]
-typing-extensions = ">=4.12.0"
-
-[[package]]
-name = "virtualenv"
-version = "20.30.0"
-description = "Virtual Python Environment builder"
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"},
- {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"},
-]
-
-[package.dependencies]
-distlib = ">=0.3.7,<1"
-filelock = ">=3.12.2,<4"
-platformdirs = ">=3.9.1,<5"
-
-[package.extras]
-docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
-test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
-
-[metadata]
-lock-version = "2.1"
-python-versions = "^3.9"
-content-hash = "6a9f851597a57497c4f351548dc17ced608124ba817dbc10d65a9b49c51be3ed"
diff --git a/pyproject.toml b/pyproject.toml
index e6bda47..40fc824 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,61 +1,95 @@
-[tool.poetry]
+[project]
name = "perigon"
-version = "0.0.0"
+version = "2.0.0"
description = "Perigon API"
-authors = ["Perigon Support "]
-license = "NoLicense"
readme = "README.md"
-repository = "https://github.com/goperigon/perigon-python"
+requires-python = ">=3.11"
+license = "MIT"
+license-files = ["LICENSE"]
+authors = [
+ { name = "Perigon Support", email = "data@perigon.io" },
+]
keywords = ["OpenAPI", "OpenAPI-Generator", "Perigon API"]
-include = ["perigon/py.typed"]
-
-[tool.poetry.dependencies]
-python = "^3.9"
-
-# ---- core runtime dependencies ----
-httpx = ">= 0.27, < 1.0"
-python-dateutil = ">= 2.8.2"
-pydantic = ">= 2"
-typing-extensions = ">= 4.7.1"
-
-
-[tool.poetry.group.dev.dependencies]
-isort = ">= 6.0.1"
-black = ">=25.1.0"
-pytest = ">= 7.2.1"
-pytest-cov = ">= 2.8.1"
-tox = ">= 3.9.0"
-flake8 = ">= 4.0.0"
-types-python-dateutil = ">= 2.8.19.14"
-mypy = ">= 1.5"
+dependencies = [
+ "httpx>=0.28.1",
+ "pydantic>=2.11.3",
+ "pydantic-core>=2.50.4",
+ "python-dateutil>=2.8.2",
+ "typing-extensions>=4.7.1",
+]
-[tool.poetry-dynamic-versioning]
-enable = true # plugin only activates if this flag is on
+[project.urls]
+Homepage = "https://perigon.io"
+Documentation = "https://perigon.io/docs/api/intro"
+Repository = "https://github.com/goperigon/perigon-python"
+Issues = "https://github.com/goperigon/perigon-python/issues"
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["uv_build>=0.13.7,<0.14.0"]
+build-backend = "uv_build"
-[tool.pylint.'MESSAGES CONTROL']
-extension-pkg-whitelist = "pydantic"
+[dependency-groups]
+dev = [
+ "mypy>=1.5",
+ "openapi-generator-cli==7.27.0",
+ "pytest>=9.0.3",
+ "pytest-asyncio>=1.3.0",
+ "pytest-cov>=2.8.1",
+ "respx>=0.23.1",
+ "ruff>=0.17.3",
+ "types-python-dateutil>=2.8.19.14",
+ "wcmatch>=12.0",
+]
[tool.mypy]
-files = [
- "perigon",
- "tests",
-]
+files = ["src/perigon", "examples", "scripts", "tests"]
+plugins = ["pydantic.mypy"]
+# TODO: enable "strict" once all these individual checks are passing
+# strict = true
+
+# List from: https://mypy.readthedocs.io/en/stable/existing_code.html#introduce-stricter-options
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
+
+## Getting these passing should be easy
strict_equality = true
extra_checks = true
+
+## Strongly recommend enabling this one as soon as you can
check_untyped_defs = true
+
+## These shouldn't be too much additional work, but may be tricky to
+## get passing if you use a lot of untyped libraries
disallow_subclassing_any = true
disallow_untyped_decorators = true
disallow_any_generics = true
-disallow_untyped_calls = true
-disallow_incomplete_defs = true
-disallow_untyped_defs = true
-no_implicit_reexport = true
-warn_return_any = true
+
+### These next few are various gradations of forcing use of type annotations
+# disallow_untyped_calls = true
+# disallow_incomplete_defs = true
+# disallow_untyped_defs = true
+
+### This one isn't too hard to get passing, but return on investment is lower
+# no_implicit_reexport = true
+
+### This one can be tricky to get passing if you use a lot of untyped libraries
+# warn_return_any = true
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "session"
+testpaths = ["tests"]
+markers = [
+ "integration: live Perigon API tests; run with pytest --integration",
+]
+
+[tool.ruff]
+line-length = 88
+
+[tool.ruff.lint]
+select = ["E", "F", "W", "I", "RUF100"]
+
+[tool.ruff.lint.per-file-ignores]
+"src/perigon/**" = ["E501"]
diff --git a/python.config.json b/python.config.json
deleted file mode 100644
index 9d0ff72..0000000
--- a/python.config.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "pypiName": "perigon",
- "packageImportName": "perigon",
- "moduleName": "PerigonClient",
- "supportsPython": ">=3.9",
- "rtdSlug": "perigon",
- "githubOrg": "goperigon",
- "githubRepo": "perigon-python",
- "pyTyped": true
-}
diff --git a/scripts/generate.sh b/scripts/generate.sh
index 4e6bf3d..2d1ff3d 100755
--- a/scripts/generate.sh
+++ b/scripts/generate.sh
@@ -1,28 +1,34 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -euo pipefail
-# Preprocess the OpenAPI spec to strip fields that should not appear in the
-# public Python SDK (e.g. Article.vectors which is reserved for specific
-# customers). The helper writes the modified spec to disk and prints its path.
-echo "Preprocessing OpenAPI spec..."
-SPEC_PATH="$(poetry run python scripts/preprocess_spec.py)"
+# 1. Download the remote Perigon OpenAPI spec and preprocess it: keep a single
+# v1 tag when an operation lists several, hidden fields and excluded paths
+# from scripts/spec-filters.json, the Article/NewsCluster cycle split, and
+# orphan-schema pruning. The snapshot is written to
+# .openapi-spec/spec.local.json.
+uv run python scripts/preprocess_spec.py
-# Generate new SDK using OpenAPI Generator
-echo "Generating new SDK from $SPEC_PATH..."
-poetry run openapi-generator-cli generate \
+# 2. Generate the Python SDK from that snapshot.
+# -c openapi-config.yaml
+# package name, httpx library, src layout, and the other generator options
+# -g python
+# --global-property apiDocs=false,modelDocs=false,apiTests=false,modelTests=false
+# skip generated docs and tests
+# -i .openapi-spec/spec.local.json
+# -t templates
+# custom Mustache templates
+#
+uv run openapi-generator-cli generate \
+ -c openapi-config.yaml \
-g python \
- -i "$SPEC_PATH" \
- -o ./ \
- -t templates \
- -c python.config.json \
- --package-name perigon \
- --global-property=apiDocs=false,modelDocs=false
+ --global-property apiDocs=false,modelDocs=false,apiTests=false,modelTests=false \
+ -i .openapi-spec/spec.local.json \
+ -t templates
-# Format the generated code
-echo "Formatting generated code with isort..."
-poetry run isort ./
+# 3. Lint and format the generated package.
+uv run ruff check --fix
+uv run ruff format
-echo "Formatting generated code with black..."
-poetry run black ./
-
-echo "SDK generation and formatting complete!"
+# 4. Typecheck, then run the test suite.
+uv run mypy
+uv run pytest
diff --git a/scripts/preprocess_spec.py b/scripts/preprocess_spec.py
old mode 100755
new mode 100644
index a6d1a85..bd7d15f
--- a/scripts/preprocess_spec.py
+++ b/scripts/preprocess_spec.py
@@ -1,98 +1,575 @@
#!/usr/bin/env python3
-"""Preprocess the Perigon OpenAPI spec before SDK generation.
+"""Pre-generation step for ``scripts/generate.sh``.
-This script downloads the public Perigon OpenAPI spec and applies any local
-adjustments needed before the SDK is generated by openapi-generator-cli.
+Downloads the remote Perigon OpenAPI spec and writes a local snapshot to
+``.openapi-spec/spec.local.json`` for the generator.
-Currently it strips ``Article.vectors`` so the field is hidden from the
-generated SDK. ``vectors`` is reserved for specific customers and is
-temporarily hidden in the public Python SDK. The wrapper-level
-``ScoredDataArticle.vectors`` and ``ScoredDataWikiData.vectors`` fields used
-by the vector-search endpoints (/v1/vector/news/all, /v1/vector/wikipedia/all)
-are intentionally left intact.
+Transformations applied before the generator runs:
-Usage:
- poetry run python scripts/preprocess_spec.py
+1. Operation tags: an operation with no tags, or exactly one tag, is left
+ unchanged. Untagged operations stay on the generator's ``DefaultApi``.
+ When ``tags`` lists ``v1`` and at least one other tag, ``tags`` becomes
+ ``["v1"]`` so the operation is emitted once. Several tags and no ``v1``
+ stops generation.
+2. Hidden properties: for each ``: [, ...]`` entry in
+ ``scripts/spec-filters.json`` the matching property is removed from
+ ``components.schemas.`` and from its ``required`` list.
+3. Excluded paths: every ``paths`` key that matches a listed glob is deleted
+ so those operations never reach the generator. A pattern with no wildcards
+ is an exact match. ``*`` covers one segment and ``**`` covers nested paths.
+ Brace expansion is disabled, so ``{uuid}`` stays literal.
+4. Article/NewsCluster cycle: ``NewsClusterArticle`` is ``Article`` without
+ ``cluster``, and ``ArticleNewsCluster`` is ``NewsCluster`` without
+ ``selectedArticles``. The nested refs point at those clones. Other
+ ``Article`` and ``NewsCluster`` refs stay on the full schemas.
+5. Orphan schemas: a fixed-point pass deletes any ``components.schemas`` entry
+ with no remaining ``$ref`` from outside that schema. Hiding a property or
+ dropping a path then also removes schemas that nothing else uses.
-The path of the resulting modified spec file is printed to stdout so the
-caller (typically scripts/generate.sh) can pass it directly to
-openapi-generator-cli via the ``-i`` flag.
+Usage:
+ uv run python scripts/preprocess_spec.py
"""
from __future__ import annotations
import json
import sys
+import traceback
+import urllib.error
import urllib.request
from pathlib import Path
-from typing import Any, Dict
+from typing import Any
+
+from pydantic import BaseModel, ConfigDict, Field
+from wcmatch import glob
SPEC_URL = "https://api.perigon.io/v1/openapi/public-sdk"
+PACKAGE_ROOT = Path(__file__).resolve().parent.parent
+CONFIG_PATH = PACKAGE_ROOT / "scripts" / "spec-filters.json"
+OUTPUT_PATH = PACKAGE_ROOT / ".openapi-spec" / "spec.local.json"
+
+# Match minimatch with nobrace, nocomment, and POSIX separators: `*` is one
+# path segment, `**` crosses segments, and `{uuid}` stays literal.
+_GLOB_FLAGS = glob.GLOBSTAR | glob.FORCEUNIX
+
+USER_AGENT = "perigon-python-sdk-generator/1.0"
+
+_V1_TAG = "v1"
+_HTTP_METHODS = frozenset(
+ {"get", "put", "post", "delete", "options", "head", "patch", "trace"}
+)
-# Map of schema name -> property name to remove from that schema.
-# Add additional entries here if more fields need to be hidden from the SDK.
-PROPERTIES_TO_STRIP: Dict[str, str] = {
- "Article": "vectors",
-}
-OUT_PATH = Path(__file__).resolve().parent.parent / ".openapi-spec.modified.json"
+class SpecFiltersConfig(BaseModel):
+ """Filters applied to the OpenAPI spec before SDK generation."""
+ model_config = ConfigDict(populate_by_name=True)
-def fetch_spec(url: str) -> Dict[str, Any]:
- # Some API gateways reject the default urllib user-agent, so set one.
+ description: str = Field(alias="$description")
+ hidden_fields: dict[str, list[str]] = Field(
+ default_factory=dict,
+ alias="hiddenFields",
+ )
+ excluded_paths: list[str] = Field(default_factory=list, alias="excludedPaths")
+
+
+def fetch_spec(url: str) -> dict[str, Any]:
+ print(f"Fetching OpenAPI spec from {url}")
request = urllib.request.Request(
url,
headers={
- "User-Agent": "perigon-python-sdk-generator/1.0",
+ "User-Agent": USER_AGENT,
"Accept": "application/json",
},
)
- with urllib.request.urlopen(request) as resp:
- return json.load(resp)
-
-
-def strip_properties(spec: Dict[str, Any]) -> None:
- schemas = spec.get("components", {}).get("schemas", {})
- for schema_name, property_name in PROPERTIES_TO_STRIP.items():
- schema = schemas.get(schema_name)
- if not schema:
- print(
- f"warning: schema {schema_name!r} not found in spec; skipping",
- file=sys.stderr,
- )
+ try:
+ with urllib.request.urlopen(request) as response:
+ payload = json.load(response)
+ except urllib.error.HTTPError as exc:
+ raise RuntimeError(
+ f"Failed to fetch spec: HTTP {exc.code} {exc.reason}"
+ ) from exc
+ if not isinstance(payload, dict):
+ raise RuntimeError("Failed to fetch spec: response was not a JSON object")
+ return payload
+
+
+def load_spec_filters(path: Path) -> SpecFiltersConfig:
+ return SpecFiltersConfig.model_validate_json(path.read_text(encoding="utf-8"))
+
+
+def _schemas(spec: dict[str, Any]) -> dict[str, Any]:
+ components = spec.get("components")
+ if not isinstance(components, dict):
+ return {}
+ schemas = components.get("schemas")
+ if not isinstance(schemas, dict):
+ return {}
+ return schemas
+
+
+def _without(mapping: dict[str, Any], *keys: str) -> dict[str, Any]:
+ excluded = set(keys)
+ return {key: value for key, value in mapping.items() if key not in excluded}
+
+
+def _with_schemas(spec: dict[str, Any], schemas: dict[str, Any]) -> dict[str, Any]:
+ components = spec.get("components")
+ if not isinstance(components, dict):
+ raise RuntimeError("OpenAPI spec is missing components")
+ return {**spec, "components": {**components, "schemas": schemas}}
+
+
+def _schema_without_property(
+ schema: dict[str, Any],
+ field: str,
+) -> dict[str, Any] | None:
+ """Return ``schema`` without ``field``, or None when the field is absent."""
+ properties = schema.get("properties")
+ if not isinstance(properties, dict) or not properties.get(field):
+ return None
+ updated = {**schema, "properties": _without(properties, field)}
+ required = schema.get("required")
+ if not isinstance(required, list):
+ return updated
+ kept = [item for item in required if item != field]
+ if kept:
+ return {**updated, "required": kept}
+ return _without(updated, "required")
+
+
+def hide_properties(
+ spec: dict[str, Any],
+ hidden: dict[str, list[str]],
+) -> tuple[dict[str, Any], int, list[str]]:
+ """Remove hidden properties.
+
+ Returns a new spec, the count applied, and entries skipped.
+ Unchanged schema objects are reused. The input spec is not modified.
+ """
+ schemas = _schemas(spec)
+ new_schemas = dict(schemas)
+ applied = 0
+ missing: list[str] = []
+
+ for schema_name, fields in hidden.items():
+ schema = new_schemas.get(schema_name)
+ if not isinstance(schema, dict):
+ missing.append(f"{schema_name} (schema not found)")
+ continue
+ for field in fields:
+ updated = _schema_without_property(schema, field)
+ if updated is None:
+ missing.append(f"{schema_name}.{field} (property not found)")
+ continue
+ schema = updated
+ applied += 1
+ print(f" Hid {schema_name}.{field}")
+ new_schemas[schema_name] = schema
+
+ if applied == 0:
+ return spec, applied, missing
+ return _with_schemas(spec, new_schemas), applied, missing
+
+
+def path_matches(path: str, pattern: str) -> bool:
+ return bool(glob.globmatch(path, pattern, flags=_GLOB_FLAGS))
+
+
+def exclude_paths(
+ spec: dict[str, Any],
+ patterns: list[str],
+) -> tuple[dict[str, Any], list[str], list[str]]:
+ """Delete matching paths.
+
+ Returns a new spec, removed paths, and patterns that matched nothing.
+ Kept path items are reused. The input spec is not modified.
+ """
+ paths = spec.get("paths")
+ if not isinstance(paths, dict):
+ return spec, [], list(patterns)
+
+ unused = set(patterns)
+ excluded: list[str] = []
+ kept: dict[str, Any] = {}
+ for path, path_item in paths.items():
+ if not isinstance(path, str):
+ kept[path] = path_item
+ continue
+ matched = False
+ for pattern in patterns:
+ if not path_matches(path, pattern):
+ continue
+ unused.discard(pattern)
+ matched = True
+ if not matched:
+ kept[path] = path_item
+ continue
+ excluded.append(path)
+ print(f" Excluded path {path}")
+
+ if not excluded:
+ return spec, excluded, [pattern for pattern in patterns if pattern in unused]
+ return (
+ {**spec, "paths": kept},
+ excluded,
+ [pattern for pattern in patterns if pattern in unused],
+ )
+
+
+_ARTICLE = "Article"
+_NEWS_CLUSTER = "NewsCluster"
+_NEWS_CLUSTER_ARTICLE = "NewsClusterArticle"
+_ARTICLE_NEWS_CLUSTER = "ArticleNewsCluster"
+
+_NEWS_CLUSTER_ARTICLE_DESCRIPTION = (
+ "Article embedded in a story cluster, without the parent cluster."
+)
+_ARTICLE_NEWS_CLUSTER_DESCRIPTION = (
+ "Story cluster embedded on an article, without selected articles."
+)
+
+
+def _schema_ref(name: str) -> str:
+ return f"#/components/schemas/{name}"
+
+
+def _require_schema(schemas: dict[str, Any], name: str) -> dict[str, Any]:
+ schema = schemas.get(name)
+ if not isinstance(schema, dict):
+ raise RuntimeError(
+ f"Cannot split Article/NewsCluster cycle: schema {name} not found"
+ )
+ return schema
+
+
+def _require_properties(schema: dict[str, Any], schema_name: str) -> dict[str, Any]:
+ properties = schema.get("properties")
+ if not isinstance(properties, dict):
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: "
+ f"{schema_name}.properties is missing"
+ )
+ return properties
+
+
+def _drop_property(
+ schema: dict[str, Any],
+ schema_name: str,
+ field: str,
+) -> dict[str, Any]:
+ properties = _require_properties(schema, schema_name)
+ if field not in properties:
+ raise RuntimeError(
+ f"Cannot split Article/NewsCluster cycle: {schema_name}.{field} not found"
+ )
+ updated = {**schema, "properties": _without(properties, field)}
+ required = schema.get("required")
+ if not isinstance(required, list):
+ return updated
+ kept = [item for item in required if item != field]
+ if kept:
+ return {**updated, "required": kept}
+ return _without(updated, "required")
+
+
+def _retarget_all_of_ref(
+ property_schema: dict[str, Any],
+ expected: str,
+ replacement: str,
+) -> dict[str, Any]:
+ all_of = property_schema.get("allOf")
+ if not isinstance(all_of, list):
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: Article.cluster.allOf is missing"
+ )
+ updated_entries: list[Any] = []
+ found = False
+ for entry in all_of:
+ if not isinstance(entry, dict) or entry.get("$ref") != expected:
+ updated_entries.append(entry)
+ continue
+ updated_entries.append({**entry, "$ref": replacement})
+ found = True
+ if not found:
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: "
+ "Article.cluster.allOf does not reference NewsCluster"
+ )
+ return {**property_schema, "allOf": updated_entries}
+
+
+def _retarget_items_ref(
+ property_schema: dict[str, Any],
+ expected: str,
+ replacement: str,
+) -> dict[str, Any]:
+ items = property_schema.get("items")
+ if not isinstance(items, dict) or items.get("$ref") != expected:
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: "
+ "NewsCluster.selectedArticles.items does not reference Article"
+ )
+ return {**property_schema, "items": {**items, "$ref": replacement}}
+
+
+def split_article_cluster_cycle(spec: dict[str, Any]) -> dict[str, Any]:
+ """Replace the Article β NewsCluster cycle with two non-cyclic slices.
+
+ ``NewsClusterArticle`` is ``Article`` without ``cluster``.
+ ``ArticleNewsCluster`` is ``NewsCluster`` without ``selectedArticles``.
+ Only the nested refs are retargeted; top-level payloads keep the full schemas.
+ Returns a new spec. The input spec is not modified.
+ """
+ schemas = _schemas(spec)
+ if not schemas:
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: components.schemas is missing"
+ )
+
+ article = _require_schema(schemas, _ARTICLE)
+ cluster = _require_schema(schemas, _NEWS_CLUSTER)
+ article_properties = _require_properties(article, _ARTICLE)
+ cluster_properties = _require_properties(cluster, _NEWS_CLUSTER)
+ cluster_property = article_properties.get("cluster")
+ selected_articles = cluster_properties.get("selectedArticles")
+ if not isinstance(cluster_property, dict):
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: Article.cluster is missing"
+ )
+ if not isinstance(selected_articles, dict):
+ raise RuntimeError(
+ "Cannot split Article/NewsCluster cycle: "
+ "NewsCluster.selectedArticles is missing"
+ )
+
+ news_cluster_article = {
+ **_drop_property(article, _ARTICLE, "cluster"),
+ "description": _NEWS_CLUSTER_ARTICLE_DESCRIPTION,
+ }
+ article_news_cluster = {
+ **_drop_property(cluster, _NEWS_CLUSTER, "selectedArticles"),
+ "description": _ARTICLE_NEWS_CLUSTER_DESCRIPTION,
+ }
+ article_ref = _schema_ref(_ARTICLE)
+ cluster_ref = _schema_ref(_NEWS_CLUSTER)
+ new_article = {
+ **article,
+ "properties": {
+ **article_properties,
+ "cluster": _retarget_all_of_ref(
+ cluster_property,
+ cluster_ref,
+ _schema_ref(_ARTICLE_NEWS_CLUSTER),
+ ),
+ },
+ }
+ new_cluster = {
+ **cluster,
+ "properties": {
+ **cluster_properties,
+ "selectedArticles": _retarget_items_ref(
+ selected_articles,
+ article_ref,
+ _schema_ref(_NEWS_CLUSTER_ARTICLE),
+ ),
+ },
+ }
+ print(
+ " Split Article/NewsCluster cycle into "
+ f"{_NEWS_CLUSTER_ARTICLE} and {_ARTICLE_NEWS_CLUSTER}"
+ )
+ return _with_schemas(
+ spec,
+ {
+ **schemas,
+ _ARTICLE: new_article,
+ _NEWS_CLUSTER: new_cluster,
+ _NEWS_CLUSTER_ARTICLE: news_cluster_article,
+ _ARTICLE_NEWS_CLUSTER: article_news_cluster,
+ },
+ )
+
+
+def collect_refs(node: object, acc: set[str]) -> None:
+ """Collect every ``$ref`` string in ``node``."""
+ if isinstance(node, dict):
+ for key, value in node.items():
+ if key == "$ref" and isinstance(value, str):
+ acc.add(value)
+ else:
+ collect_refs(value, acc)
+ elif isinstance(node, list):
+ for item in node:
+ collect_refs(item, acc)
+
+
+def prune_orphan_schemas(spec: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
+ """Delete schemas with no ``$ref`` from outside themselves.
+
+ A schema that only references itself is removed. Schemas that reference
+ each other stay, matching the TypeScript preprocessor: a ref counts when
+ it comes from any other node. Returns a new spec and the pruned names.
+ The input spec is not modified.
+ """
+ components = spec.get("components")
+ if not isinstance(components, dict):
+ return spec, []
+ original_schemas = components.get("schemas")
+ if not isinstance(original_schemas, dict) or not original_schemas:
+ return spec, []
+
+ schemas = dict(original_schemas)
+ pruned: list[str] = []
+ while True:
+ view = {**spec, "components": {**components, "schemas": schemas}}
+ all_refs: set[str] = set()
+ collect_refs(view, all_refs)
+
+ removed_this_pass = 0
+ for name in list(schemas):
+ target = f"#/components/schemas/{name}"
+ if target in all_refs:
+ saved = schemas[name]
+ del schemas[name]
+ refs_without_self: set[str] = set()
+ collect_refs(
+ {**spec, "components": {**components, "schemas": schemas}},
+ refs_without_self,
+ )
+ if target not in refs_without_self:
+ pruned.append(name)
+ print(f" Pruned orphan schema {name}")
+ removed_this_pass += 1
+ continue
+ schemas[name] = saved
+ else:
+ del schemas[name]
+ pruned.append(name)
+ print(f" Pruned orphan schema {name}")
+ removed_this_pass += 1
+
+ if removed_this_pass == 0:
+ break
+
+ if not pruned:
+ return spec, pruned
+ return _with_schemas(spec, schemas), pruned
+
+
+def prefer_v1_tag(spec: dict[str, Any]) -> dict[str, Any]:
+ """Keep a single ``v1`` tag when an operation lists several tags.
+
+ An operation with no tags, or exactly one tag, is left unchanged. The
+ generator assigns untagged operations to ``DefaultApi``.
+
+ When ``tags`` contains ``v1`` and at least one other tag, ``tags`` is
+ replaced with ``["v1"]``. When it contains several tags and ``v1`` is
+ not among them, generation stops. The input spec is not modified.
+ """
+ paths = spec.get("paths")
+ if not isinstance(paths, dict):
+ return spec
+
+ new_paths: dict[str, Any] = {}
+ changed = False
+ for path, path_item in paths.items():
+ if not isinstance(path, str) or not isinstance(path_item, dict):
+ new_paths[path] = path_item
continue
- properties = schema.get("properties", {})
- removed = properties.pop(property_name, None)
- if removed is not None:
- print(
- f"stripped {schema_name}.{property_name} from spec",
- file=sys.stderr,
- )
- else:
- print(
- f"note: {schema_name}.{property_name} already absent; nothing to strip",
- file=sys.stderr,
- )
-
- # Also drop it from `required` if it happens to be listed there.
- required = schema.get("required")
- if isinstance(required, list) and property_name in required:
- required.remove(property_name)
- print(
- f"removed {property_name!r} from {schema_name}.required",
- file=sys.stderr,
- )
+
+ new_item: dict[str, Any] | None = None
+ for method, operation in path_item.items():
+ if (
+ not isinstance(method, str)
+ or method.lower() not in _HTTP_METHODS
+ or not isinstance(operation, dict)
+ ):
+ continue
+ tags = operation.get("tags")
+ if not isinstance(tags, list) or len(tags) <= 1:
+ continue
+ label = f"{method.upper()} {path}"
+ operation_id = operation.get("operationId")
+ if isinstance(operation_id, str) and operation_id:
+ label = f"{label} ({operation_id})"
+ if _V1_TAG not in tags:
+ raise RuntimeError(
+ "Operation has multiple tags and none of them is 'v1': "
+ f"{label} tags={tags}"
+ )
+ if new_item is None:
+ new_item = dict(path_item)
+ new_item[method] = {**operation, "tags": [_V1_TAG]}
+ changed = True
+ print(f" Kept v1 tag for {label} (was {tags})")
+
+ new_paths[path] = path_item if new_item is None else new_item
+
+ if not changed:
+ return spec
+ return {**spec, "paths": new_paths}
+
+
+def _warn(title: str, items: list[str]) -> None:
+ print(title, file=sys.stderr)
+ for item in items:
+ print(f" - {item}", file=sys.stderr)
def main() -> None:
- print(f"downloading spec from {SPEC_URL}", file=sys.stderr)
+ config = load_spec_filters(CONFIG_PATH)
+ hidden_entries = [
+ f"{schema_name}.{field}"
+ for schema_name, fields in config.hidden_fields.items()
+ for field in fields
+ ]
+ print("Loaded spec filters from scripts/spec-filters.json")
+ hidden_summary = f" {len(hidden_entries)} hidden field(s)"
+ if hidden_entries:
+ hidden_summary += f": {', '.join(hidden_entries)}"
+ print(hidden_summary)
+ path_summary = f" {len(config.excluded_paths)} excluded path pattern(s)"
+ if config.excluded_paths:
+ path_summary += f": {', '.join(config.excluded_paths)}"
+ print(path_summary)
+
spec = fetch_spec(SPEC_URL)
- strip_properties(spec)
- OUT_PATH.write_text(json.dumps(spec))
- print(f"wrote modified spec to {OUT_PATH}", file=sys.stderr)
- # The path on stdout is consumed by scripts/generate.sh.
- print(OUT_PATH)
+ spec = prefer_v1_tag(spec)
+
+ spec, applied, missing = hide_properties(spec, config.hidden_fields)
+ if missing:
+ _warn(
+ f"Skipped {len(missing)} hidden-field entry/entries (not present in spec):",
+ missing,
+ )
+
+ spec, excluded, unused = exclude_paths(spec, config.excluded_paths)
+ if unused:
+ _warn(
+ f"Skipped {len(unused)} excluded-path pattern(s) (no matching paths):",
+ unused,
+ )
+
+ spec = split_article_cluster_cycle(spec)
+
+ spec, pruned = prune_orphan_schemas(spec)
+
+ OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
+ OUTPUT_PATH.write_text(
+ json.dumps(spec, indent=2, ensure_ascii=False) + "\n",
+ encoding="utf-8",
+ )
+ print(
+ f"Wrote preprocessed spec to {OUTPUT_PATH} "
+ f"({applied} field(s) hidden, {len(excluded)} path(s) excluded, "
+ f"{len(pruned)} orphan schema(s) pruned"
+ )
if __name__ == "__main__":
- main()
+ try:
+ main()
+ except Exception:
+ traceback.print_exc()
+ sys.exit(1)
diff --git a/scripts/smoke_test.py b/scripts/smoke_test.py
new file mode 100644
index 0000000..c567a78
--- /dev/null
+++ b/scripts/smoke_test.py
@@ -0,0 +1,20 @@
+"""Check that an installed perigon build imports and reports its own version."""
+
+import importlib.metadata
+
+import perigon
+
+
+def main() -> None:
+ installed = importlib.metadata.version("perigon")
+ if perigon.__version__ != installed:
+ raise SystemExit(
+ f"perigon.__version__ is {perigon.__version__}, "
+ f"installed distribution is {installed}"
+ )
+ if perigon.V1Api is None or perigon.ApiClient is None:
+ raise SystemExit("perigon.V1Api and perigon.ApiClient must be importable")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/spec-filters.json b/scripts/spec-filters.json
new file mode 100644
index 0000000..cd6fa28
--- /dev/null
+++ b/scripts/spec-filters.json
@@ -0,0 +1,9 @@
+{
+ "$description": "Filters applied to the OpenAPI spec before SDK generation. `hiddenFields` is schema-name -> property names to omit from generated models and types. `excludedPaths` is a list of minimatch globs matched against OpenAPI path keys. A pattern with no wildcards is an exact match; use `*` for one segment and `**` for nested paths (e.g. `/v1/api/monitors/**`). Brace expansion is disabled so `{uuid}` stays literal. Remove an entry here to re-expose a field or endpoint.",
+ "hiddenFields": {
+ "Article": ["topCategory", "vectors"],
+ "ScoredDataArticle": ["vectors"],
+ "ScoredDataWikiData": ["vectors"]
+ },
+ "excludedPaths": ["/v1/articles/refresh", "/v1/articles/refresh/**"]
+}
diff --git a/src/perigon/__init__.py b/src/perigon/__init__.py
new file mode 100644
index 0000000..426527e
--- /dev/null
+++ b/src/perigon/__init__.py
@@ -0,0 +1,191 @@
+# coding: utf-8
+
+# flake8: noqa
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+""" # noqa: E501
+
+__version__ = "2.0.0"
+
+# import apis into sdk package
+from perigon.api.v1_api import V1Api
+from perigon.api.v1_api import AsyncV1Api
+
+# import ApiClient
+from perigon.api_response import ApiResponse
+from perigon.api_client import ApiClient, AsyncApiClient
+from perigon.configuration import Configuration
+from perigon.exceptions import OpenApiException
+from perigon.exceptions import ApiTypeError
+from perigon.exceptions import ApiValueError
+from perigon.exceptions import ApiKeyError
+from perigon.exceptions import ApiAttributeError
+from perigon.exceptions import ApiException
+
+# import models into sdk package
+from perigon.models.all_endpoint_sort_by import AllEndpointSortBy
+from perigon.models.api_key_status import ApiKeyStatus
+from perigon.models.api_limits_dto import ApiLimitsDto
+from perigon.models.article import Article
+from perigon.models.article_news_cluster import ArticleNewsCluster
+from perigon.models.article_search_filter import ArticleSearchFilter
+from perigon.models.article_search_params import ArticleSearchParams
+from perigon.models.articles_vector_search_result import ArticlesVectorSearchResult
+from perigon.models.category_holder import CategoryHolder
+from perigon.models.category_with_score_holder import CategoryWithScoreHolder
+from perigon.models.clusters_list_sort_by import ClustersListSortBy
+from perigon.models.company import Company
+from perigon.models.company_count import CompanyCount
+from perigon.models.company_holder import CompanyHolder
+from perigon.models.company_search_result import CompanySearchResult
+from perigon.models.complex_all_endpoint_query import ComplexAllEndpointQuery
+from perigon.models.contact_point_api_dto import ContactPointApiDto
+from perigon.models.contact_point_list_result import ContactPointListResult
+from perigon.models.contact_point_request_body import ContactPointRequestBody
+from perigon.models.contact_point_result import ContactPointResult
+from perigon.models.contact_point_sort_by import ContactPointSortBy
+from perigon.models.contact_point_update_body import ContactPointUpdateBody
+from perigon.models.coordinate import Coordinate
+from perigon.models.coordinate_filter import CoordinateFilter
+from perigon.models.create_source_group_params import CreateSourceGroupParams
+from perigon.models.create_watchlist_params import CreateWatchlistParams
+from perigon.models.entity_group import EntityGroup
+from perigon.models.entity_holder import EntityHolder
+from perigon.models.error_response import ErrorResponse
+from perigon.models.event_type_holder import EventTypeHolder
+from perigon.models.fillbot_metadata import FillbotMetadata
+from perigon.models.filled_field import FilledField
+from perigon.models.filled_field_source import FilledFieldSource
+from perigon.models.id_name_holder import IdNameHolder
+from perigon.models.image_holder import ImageHolder
+from perigon.models.journalist import Journalist
+from perigon.models.journalist_search_result import JournalistSearchResult
+from perigon.models.journalists_sort_by import JournalistsSortBy
+from perigon.models.key_point import KeyPoint
+from perigon.models.keyword_holder import KeywordHolder
+from perigon.models.label_holder import LabelHolder
+from perigon.models.limits_result import LimitsResult
+from perigon.models.location_count import LocationCount
+from perigon.models.location_holder import LocationHolder
+from perigon.models.matched_entity_data import MatchedEntityData
+from perigon.models.monitor_event_list_result import MonitorEventListResult
+from perigon.models.monitor_list_result import MonitorListResult
+from perigon.models.monitor_newsletter_list_result import MonitorNewsletterListResult
+from perigon.models.monitor_result import MonitorResult
+from perigon.models.monitor_summary_list_result import MonitorSummaryListResult
+from perigon.models.name_count import NameCount
+from perigon.models.news_cluster import NewsCluster
+from perigon.models.news_cluster_article import NewsClusterArticle
+from perigon.models.patch_source_group_params import PatchSourceGroupParams
+from perigon.models.people_search_result import PeopleSearchResult
+from perigon.models.person import Person
+from perigon.models.person_count import PersonCount
+from perigon.models.person_holder import PersonHolder
+from perigon.models.place import Place
+from perigon.models.query_search_result import QuerySearchResult
+from perigon.models.question import Question
+from perigon.models.record_stat_holder import RecordStatHolder
+from perigon.models.schedule_interval import ScheduleInterval
+from perigon.models.scored_data_article import ScoredDataArticle
+from perigon.models.scored_data_wiki_data import ScoredDataWikiData
+from perigon.models.sentiment_holder import SentimentHolder
+from perigon.models.signal_api_create_body import SignalApiCreateBody
+from perigon.models.signal_api_dto import SignalApiDto
+from perigon.models.signal_api_update_body import SignalApiUpdateBody
+from perigon.models.signal_article_partial_dto import SignalArticlePartialDto
+from perigon.models.signal_event_dto import SignalEventDto
+from perigon.models.signal_linked_in_query import SignalLinkedInQuery
+from perigon.models.signal_monitoring_plan_data_schema import (
+ SignalMonitoringPlanDataSchema,
+)
+from perigon.models.signal_monitoring_plan_data_schema_property import (
+ SignalMonitoringPlanDataSchemaProperty,
+)
+from perigon.models.signal_monitoring_plan_newsletter_config import (
+ SignalMonitoringPlanNewsletterConfig,
+)
+from perigon.models.signal_newsletter_dto import SignalNewsletterDto
+from perigon.models.signal_newsletter_story_partial_dto import (
+ SignalNewsletterStoryPartialDto,
+)
+from perigon.models.signal_reddit_query import SignalRedditQuery
+from perigon.models.signal_schedule_policy import SignalSchedulePolicy
+from perigon.models.signal_summary_dto import SignalSummaryDto
+from perigon.models.signal_x_query import SignalXQuery
+from perigon.models.social_linked_in_feed_post import SocialLinkedInFeedPost
+from perigon.models.social_linked_in_feed_post_author import (
+ SocialLinkedInFeedPostAuthor,
+)
+from perigon.models.social_linked_in_feed_post_comment import (
+ SocialLinkedInFeedPostComment,
+)
+from perigon.models.social_linked_in_feed_post_reaction import (
+ SocialLinkedInFeedPostReaction,
+)
+from perigon.models.social_linked_in_feed_result import SocialLinkedInFeedResult
+from perigon.models.social_linked_in_post import SocialLinkedInPost
+from perigon.models.social_linked_in_post_author import SocialLinkedInPostAuthor
+from perigon.models.social_linked_in_post_search_result import (
+ SocialLinkedInPostSearchResult,
+)
+from perigon.models.social_reddit_post import SocialRedditPost
+from perigon.models.social_reddit_post_search_result import SocialRedditPostSearchResult
+from perigon.models.social_x_post import SocialXPost
+from perigon.models.social_x_post_author import SocialXPostAuthor
+from perigon.models.social_x_post_search_result import SocialXPostSearchResult
+from perigon.models.source import Source
+from perigon.models.source_group_dto import SourceGroupDto
+from perigon.models.source_group_list_result import SourceGroupListResult
+from perigon.models.source_group_resolve_result import SourceGroupResolveResult
+from perigon.models.source_group_result import SourceGroupResult
+from perigon.models.source_group_sort_by import SourceGroupSortBy
+from perigon.models.source_holder import SourceHolder
+from perigon.models.source_location import SourceLocation
+from perigon.models.source_search_result import SourceSearchResult
+from perigon.models.source_top_stat_holder import SourceTopStatHolder
+from perigon.models.sources_sort_by import SourcesSortBy
+from perigon.models.stat_result import StatResult
+from perigon.models.story_history_record import StoryHistoryRecord
+from perigon.models.story_history_result import StoryHistoryResult
+from perigon.models.story_search_result import StorySearchResult
+from perigon.models.summary_body import SummaryBody
+from perigon.models.summary_search_result import SummarySearchResult
+from perigon.models.symbol_holder import SymbolHolder
+from perigon.models.topic_dto import TopicDto
+from perigon.models.topic_holder import TopicHolder
+from perigon.models.topic_labels import TopicLabels
+from perigon.models.topic_search_result import TopicSearchResult
+from perigon.models.update_watchlist_params import UpdateWatchlistParams
+from perigon.models.watchlist_company import WatchlistCompany
+from perigon.models.watchlist_company_dto import WatchlistCompanyDto
+from perigon.models.watchlist_dto import WatchlistDto
+from perigon.models.watchlist_list_result import WatchlistListResult
+from perigon.models.watchlist_person import WatchlistPerson
+from perigon.models.watchlist_person_dto import WatchlistPersonDto
+from perigon.models.watchlist_resolve_result import WatchlistResolveResult
+from perigon.models.watchlist_result import WatchlistResult
+from perigon.models.watchlist_sort_by import WatchlistSortBy
+from perigon.models.web_resources import WebResources
+from perigon.models.wiki_data import WikiData
+from perigon.models.wiki_page import WikiPage
+from perigon.models.wiki_page_section_holder import WikiPageSectionHolder
+from perigon.models.wikidata_date_holder import WikidataDateHolder
+from perigon.models.wikidata_label_holder import WikidataLabelHolder
+from perigon.models.wikidata_political_party_holder import WikidataPoliticalPartyHolder
+from perigon.models.wikidata_position_holder import WikidataPositionHolder
+from perigon.models.wikipedia_search_filter import WikipediaSearchFilter
+from perigon.models.wikipedia_search_params import WikipediaSearchParams
+from perigon.models.wikipedia_search_result import WikipediaSearchResult
+from perigon.models.wikipedia_vector_search_result import WikipediaVectorSearchResult
+from perigon.models.x_post import XPost
+from perigon.models.x_post_media import XPostMedia
+from perigon.models.x_post_media_item import XPostMediaItem
diff --git a/src/perigon/api/__init__.py b/src/perigon/api/__init__.py
new file mode 100644
index 0000000..ff6d0ad
--- /dev/null
+++ b/src/perigon/api/__init__.py
@@ -0,0 +1,4 @@
+# flake8: noqa
+
+# import apis into api package
+from perigon.api.v1_api import V1Api
diff --git a/perigon/api/v1_api.py b/src/perigon/api/v1_api.py
similarity index 70%
rename from perigon/api/v1_api.py
rename to src/perigon/api/v1_api.py
index 7bc1646..25ad685 100644
--- a/perigon/api/v1_api.py
+++ b/src/perigon/api/v1_api.py
@@ -1,31 +1,50 @@
-from datetime import datetime
+from __future__ import annotations
+
+from datetime import datetime, timezone
from enum import Enum
-from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
-
-from pydantic import (
- Field,
- StrictBool,
- StrictFloat,
- StrictInt,
- StrictStr,
- field_validator,
-)
-from typing_extensions import Annotated
+from typing import Any, Dict, List, Mapping, Optional
+from uuid import UUID
-from perigon.api_client import ApiClient
+from perigon.api_client import ApiClient, AsyncApiClient, bind_client
+from perigon.configuration import Configuration
from perigon.models.all_endpoint_sort_by import AllEndpointSortBy
from perigon.models.article_search_params import ArticleSearchParams
from perigon.models.articles_vector_search_result import ArticlesVectorSearchResult
+from perigon.models.clusters_list_sort_by import ClustersListSortBy
from perigon.models.company_search_result import CompanySearchResult
+from perigon.models.contact_point_list_result import ContactPointListResult
+from perigon.models.contact_point_request_body import ContactPointRequestBody
+from perigon.models.contact_point_result import ContactPointResult
+from perigon.models.contact_point_sort_by import ContactPointSortBy
+from perigon.models.contact_point_update_body import ContactPointUpdateBody
from perigon.models.create_source_group_params import CreateSourceGroupParams
from perigon.models.create_watchlist_params import CreateWatchlistParams
from perigon.models.journalist import Journalist
from perigon.models.journalist_search_result import JournalistSearchResult
+from perigon.models.journalists_sort_by import JournalistsSortBy
+from perigon.models.limits_result import LimitsResult
+from perigon.models.monitor_event_list_result import MonitorEventListResult
+from perigon.models.monitor_list_result import MonitorListResult
+from perigon.models.monitor_newsletter_list_result import MonitorNewsletterListResult
+from perigon.models.monitor_result import MonitorResult
+from perigon.models.monitor_summary_list_result import MonitorSummaryListResult
from perigon.models.patch_source_group_params import PatchSourceGroupParams
from perigon.models.people_search_result import PeopleSearchResult
from perigon.models.query_search_result import QuerySearchResult
-from perigon.models.sort_by import SortBy
+from perigon.models.signal_api_create_body import SignalApiCreateBody
+from perigon.models.signal_api_update_body import SignalApiUpdateBody
+from perigon.models.social_linked_in_feed_result import SocialLinkedInFeedResult
+from perigon.models.social_linked_in_post_search_result import (
+ SocialLinkedInPostSearchResult,
+)
+from perigon.models.social_reddit_post_search_result import SocialRedditPostSearchResult
+from perigon.models.social_x_post_search_result import SocialXPostSearchResult
+from perigon.models.source_group_list_result import SourceGroupListResult
+from perigon.models.source_group_resolve_result import SourceGroupResolveResult
+from perigon.models.source_group_result import SourceGroupResult
+from perigon.models.source_group_sort_by import SourceGroupSortBy
from perigon.models.source_search_result import SourceSearchResult
+from perigon.models.sources_sort_by import SourcesSortBy
from perigon.models.stat_result import StatResult
from perigon.models.story_history_result import StoryHistoryResult
from perigon.models.story_search_result import StorySearchResult
@@ -33,97 +52,208 @@
from perigon.models.summary_search_result import SummarySearchResult
from perigon.models.topic_search_result import TopicSearchResult
from perigon.models.update_watchlist_params import UpdateWatchlistParams
+from perigon.models.watchlist_list_result import WatchlistListResult
+from perigon.models.watchlist_resolve_result import WatchlistResolveResult
+from perigon.models.watchlist_result import WatchlistResult
+from perigon.models.watchlist_sort_by import WatchlistSortBy
from perigon.models.wikipedia_search_params import WikipediaSearchParams
from perigon.models.wikipedia_search_result import WikipediaSearchResult
from perigon.models.wikipedia_vector_search_result import WikipediaVectorSearchResult
# Define API paths
+PATH_ACTIVATE_MONITOR_API = "/v1/api/monitors/{uuid}/activate"
+PATH_ARCHIVE_MONITOR_API = "/v1/api/monitors/{uuid}"
+PATH_CREATE_CONTACT_POINT = "/v1/api/contactPoints"
+PATH_CREATE_MONITOR_API = "/v1/api/monitors"
PATH_CREATE_SOURCE_GROUP = "/v1/api/sourceGroups"
PATH_CREATE_WATCHLIST = "/v1/api/watchlists"
+PATH_DELETE_CONTACT_POINT = "/v1/api/contactPoints/{uuid}"
PATH_DELETE_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
PATH_DELETE_WATCHLIST = "/v1/api/watchlists/{id}"
+PATH_GET_CONTACT_POINT = "/v1/api/contactPoints/{uuid}"
PATH_GET_JOURNALIST_BY_ID = "/v1/journalists/{id}"
+PATH_GET_LIMITS = "/v1/limits"
+PATH_GET_LINKEDIN_COMPANY_POSTS = "/v1/social/linkedin/companies/posts"
+PATH_GET_LINKEDIN_PERSON_ACTIVITY = "/v1/social/linkedin/people/activity"
+PATH_GET_MONITOR_API = "/v1/api/monitors/{uuid}"
PATH_GET_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
PATH_GET_STORY_COUNTS = "/v1/stories/stats"
PATH_GET_STORY_HISTORY = "/v1/stories/history"
PATH_GET_WATCHLIST = "/v1/api/watchlists/{id}"
+PATH_LIST_CONTACT_POINTS = "/v1/api/contactPoints"
+PATH_LIST_MONITOR_EVENTS_API = "/v1/api/monitors/{uuid}/events"
+PATH_LIST_MONITOR_NEWSLETTERS_API = "/v1/api/monitors/{uuid}/newsletters"
+PATH_LIST_MONITOR_SUMMARIES_API = "/v1/api/monitors/{uuid}/summary"
+PATH_LIST_MONITORS_API = "/v1/api/monitors"
PATH_LIST_SOURCE_GROUPS = "/v1/api/sourceGroups"
PATH_LIST_WATCHLISTS = "/v1/api/watchlists"
+PATH_PAUSE_MONITOR_API = "/v1/api/monitors/{uuid}/pause"
PATH_RESOLVE_SOURCE_GROUPS = "/v1/api/sourceGroups/resolve"
PATH_RESOLVE_WATCHLISTS = "/v1/api/watchlists/resolve"
PATH_SEARCH_ARTICLES = "/v1/articles/all"
PATH_SEARCH_COMPANIES = "/v1/companies/all"
PATH_SEARCH_JOURNALISTS = "/v1/journalists/all"
+PATH_SEARCH_LINKEDIN_POSTS = "/v1/social/linkedin/posts"
PATH_SEARCH_PEOPLE = "/v1/people/all"
+PATH_SEARCH_REDDIT_POSTS = "/v1/social/reddit/posts"
PATH_SEARCH_SOURCES = "/v1/sources/all"
PATH_SEARCH_STORIES = "/v1/stories/all"
PATH_SEARCH_SUMMARIZER = "/v1/summarize"
PATH_SEARCH_TOPICS = "/v1/topics/all"
PATH_SEARCH_WIKIPEDIA = "/v1/wikipedia/all"
+PATH_SEARCH_X_POSTS = "/v1/social/x/posts"
+PATH_UPDATE_CONTACT_POINT = "/v1/api/contactPoints/{uuid}"
+PATH_UPDATE_MONITOR_API = "/v1/api/monitors/{uuid}"
PATH_UPDATE_SOURCE_GROUP = "/v1/api/sourceGroups/{id}"
PATH_UPDATE_WATCHLIST = "/v1/api/watchlists/{id}"
PATH_VECTOR_SEARCH_ARTICLES = "/v1/vector/news/all"
PATH_VECTOR_SEARCH_WIKIPEDIA = "/v1/vector/wikipedia/all"
+def _format_query_value(value: Any) -> str:
+ """Format one query value for the Perigon API."""
+ if isinstance(value, Enum):
+ value = value.value
+ if isinstance(value, datetime):
+ if value.tzinfo is not None:
+ value = value.astimezone(timezone.utc)
+ return value.strftime("%Y-%m-%dT%H:%M:%S")
+ if isinstance(value, bool):
+ return "true" if value else "false"
+ return str(value)
+
+
def _normalise_query(params: Mapping[str, Any]) -> Dict[str, Any]:
"""
- β’ Convert Enum β Enum.value
- β’ Convert list/tuple/set β CSV string (after Enum handling)
- β’ Skip None values
+ β’ Convert Enum to its value
+ β’ Format datetime as YYYY-MM-DDTHH:MM:SS in UTC, without an offset
+ β’ Format bool as true/false
+ β’ Keep list/tuple/set as a list so the key is repeated
+ β’ Skip None values and empty collections
"""
out: Dict[str, Any] = {}
for key, value in params.items():
- if value is None: # ignore "unset"
+ if value is None:
+ continue
+
+ if isinstance(value, (list, tuple, set)):
+ items = [_format_query_value(item) for item in value if item is not None]
+ if items:
+ out[key] = items
continue
- # Unwrap single Enum
- if isinstance(value, Enum): # Enum β str
- value = value.value
-
- # Handle datetime objects properly
- from datetime import datetime
-
- if isinstance(value, datetime):
- value = value.isoformat().split("+")[0]
-
- # Handle collection (after possible Enum unwrap)
- elif isinstance(value, (list, tuple, set)):
- # unwrap Enum members inside the collection
- items: Iterable[str] = (
- (
- item.isoformat().replace(" ", "+")
- if isinstance(item, datetime)
- else str(item.value if isinstance(item, Enum) else item)
- )
- for item in value
- )
- value = ",".join(items) # CSV join
- else:
- value = str(value)
-
- out[key] = value
+ out[key] = _format_query_value(value)
return out
+def _json_body(body: Any) -> Any:
+ """Serialize a request model.
+
+ Only fields the caller passed are included. An explicit null is kept so a
+ PATCH can clear that field. Unset defaults are omitted. mode="json" converts
+ datetime and UUID values, including inside nested models.
+ """
+ return body.model_dump(mode="json", by_alias=True, exclude_unset=True)
+
+
class V1Api:
""""""
- def __init__(self, api_client: Optional[ApiClient] = None):
- self.api_client = api_client or ApiClient()
+ def __init__(
+ self,
+ api_client: Optional[ApiClient] = None,
+ *,
+ api_key: Optional[str] = None,
+ base_url: Optional[str] = None,
+ timeout: Optional[float] = None,
+ configuration: Optional[Configuration] = None,
+ ) -> None:
+ self.api_client, self._owns_client = bind_client(
+ api_client,
+ ApiClient,
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ )
+
+ def __enter__(self) -> V1Api:
+ return self
- # ----------------- create_source_group (sync) ----------------- #
- def create_source_group(self, create_source_group_params: CreateSourceGroupParams):
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ self.close()
+
+ def close(self) -> None:
+ """Close the HTTP client when this API created it."""
+ if self._owns_client:
+ self.api_client.close()
+
+ # ----------------- activate_monitor_api (sync) ----------------- #
+ def activate_monitor_api(self, uuid: UUID) -> MonitorResult:
"""
- Create a new source group under the organization associated with the API key.
+ Activate a monitor, starting its processing pipeline.
Args:
- create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
+ uuid (UUID): UUID of the monitor to activate. (required)
+ Returns:
+ MonitorResult: The response
"""
# Get path template from class attribute
- path = PATH_CREATE_SOURCE_GROUP
+ path = PATH_ACTIVATE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("POST", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- archive_monitor_api (sync) ----------------- #
+ def archive_monitor_api(self, uuid: UUID) -> MonitorResult:
+ """
+ Archive a monitor. Archived monitors are excluded from list results by default.
+
+ Args:
+ uuid (UUID): UUID of the monitor to archive. (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_ARCHIVE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("DELETE", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- create_contact_point (sync) ----------------- #
+ def create_contact_point(
+ self, contact_point_request_body: ContactPointRequestBody
+ ) -> ContactPointResult:
+ """
+ Create a new contact point under the organization associated with the API key. Only WEBHOOK contact points can be created via the API.
+
+ Args:
+ contact_point_request_body (ContactPointRequestBody): Parameter contact_point_request_body (required)
+
+ Returns:
+ ContactPointResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_CREATE_CONTACT_POINT
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
@@ -133,52 +263,55 @@ def create_source_group(self, create_source_group_params: CreateSourceGroupParam
"POST",
path,
params=params,
- json=create_source_group_params.model_dump(
- by_alias=True, exclude_none=True
- ),
+ json=_json_body(contact_point_request_body),
)
resp.raise_for_status()
- return resp.json()
+ return ContactPointResult.model_validate(resp.json())
- # ----------------- create_source_group (async) ----------------- #
- async def create_source_group_async(
- self, create_source_group_params: CreateSourceGroupParams
- ):
+ # ----------------- create_monitor_api (sync) ----------------- #
+ def create_monitor_api(
+ self, signal_api_create_body: SignalApiCreateBody
+ ) -> MonitorResult:
"""
- Async variant of create_source_group. Create a new source group under the organization associated with the API key.
+ Create a new AI monitor. Only AI_SIGNAL type is supported; status is required and accepts ACTIVE or DRAFT.
Args:
- create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
+ signal_api_create_body (SignalApiCreateBody): Parameter signal_api_create_body (required)
+ Returns:
+ MonitorResult: The response
"""
# Get path template from class attribute
- path = PATH_CREATE_SOURCE_GROUP
+ path = PATH_CREATE_MONITOR_API
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = self.api_client.request(
"POST",
path,
params=params,
- json=create_source_group_params.model_dump(
- by_alias=True, exclude_none=True
- ),
+ json=_json_body(signal_api_create_body),
)
resp.raise_for_status()
- return resp.json()
+ return MonitorResult.model_validate(resp.json())
- # ----------------- create_watchlist (sync) ----------------- #
- def create_watchlist(self, create_watchlist_params: CreateWatchlistParams):
+ # ----------------- create_source_group (sync) ----------------- #
+ def create_source_group(
+ self, create_source_group_params: CreateSourceGroupParams
+ ) -> SourceGroupResult:
"""
- Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
+ Create a new source group under the organization associated with the API key.
Args:
- create_watchlist_params (CreateWatchlistParams): Parameter create_watchlist_params (required)
+ create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
+ Returns:
+ SourceGroupResult: The response
"""
# Get path template from class attribute
- path = PATH_CREATE_WATCHLIST
+ path = PATH_CREATE_SOURCE_GROUP
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
@@ -188,51 +321,56 @@ def create_watchlist(self, create_watchlist_params: CreateWatchlistParams):
"POST",
path,
params=params,
- json=create_watchlist_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(create_source_group_params),
)
resp.raise_for_status()
- return resp.json()
+ return SourceGroupResult.model_validate(resp.json())
- # ----------------- create_watchlist (async) ----------------- #
- async def create_watchlist_async(
+ # ----------------- create_watchlist (sync) ----------------- #
+ def create_watchlist(
self, create_watchlist_params: CreateWatchlistParams
- ):
+ ) -> WatchlistResult:
"""
- Async variant of create_watchlist. Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
+ Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
Args:
create_watchlist_params (CreateWatchlistParams): Parameter create_watchlist_params (required)
+ Returns:
+ WatchlistResult: The response
"""
# Get path template from class attribute
path = PATH_CREATE_WATCHLIST
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = self.api_client.request(
"POST",
path,
params=params,
- json=create_watchlist_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(create_watchlist_params),
)
resp.raise_for_status()
- return resp.json()
+ return WatchlistResult.model_validate(resp.json())
- # ----------------- delete_source_group (sync) ----------------- #
- def delete_source_group(self, id: int):
+ # ----------------- delete_contact_point (sync) ----------------- #
+ def delete_contact_point(self, uuid: UUID) -> ContactPointResult:
"""
- Delete a source group owned by the organization associated with the API key.
+ Delete a contact point owned by the organization associated with the API key. Contact points with historical notifications are archived rather than hard-deleted.
Args:
- id (int): Parameter id (required)
+ uuid (UUID): Parameter uuid (required)
+ Returns:
+ ContactPointResult: The response
"""
# Get path template from class attribute
- path = PATH_DELETE_SOURCE_GROUP
+ path = PATH_DELETE_CONTACT_POINT
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
@@ -240,16 +378,18 @@ def delete_source_group(self, id: int):
resp = self.api_client.request("DELETE", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return ContactPointResult.model_validate(resp.json())
- # ----------------- delete_source_group (async) ----------------- #
- async def delete_source_group_async(self, id: int):
+ # ----------------- delete_source_group (sync) ----------------- #
+ def delete_source_group(self, id: int) -> SourceGroupResult:
"""
- Async variant of delete_source_group. Delete a source group owned by the organization associated with the API key.
+ Delete a source group owned by the organization associated with the API key.
Args:
id (int): Parameter id (required)
+ Returns:
+ SourceGroupResult: The response
"""
# Get path template from class attribute
path = PATH_DELETE_SOURCE_GROUP
@@ -257,21 +397,24 @@ async def delete_source_group_async(self, id: int):
# Replace path parameters
path = path.format(id=str(id))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async("DELETE", path, params=params)
+ resp = self.api_client.request("DELETE", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return SourceGroupResult.model_validate(resp.json())
# ----------------- delete_watchlist (sync) ----------------- #
- def delete_watchlist(self, id: int):
+ def delete_watchlist(self, id: int) -> WatchlistResult:
"""
- Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active signals.
+ Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active monitors.
Args:
id (int): Parameter id (required)
+ Returns:
+ WatchlistResult: The response
"""
# Get path template from class attribute
path = PATH_DELETE_WATCHLIST
@@ -285,29 +428,32 @@ def delete_watchlist(self, id: int):
resp = self.api_client.request("DELETE", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return WatchlistResult.model_validate(resp.json())
- # ----------------- delete_watchlist (async) ----------------- #
- async def delete_watchlist_async(self, id: int):
+ # ----------------- get_contact_point (sync) ----------------- #
+ def get_contact_point(self, uuid: UUID) -> ContactPointResult:
"""
- Async variant of delete_watchlist. Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active signals.
+ Retrieve a contact point by UUID. Only returns contact points owned by the organization associated with the API key. FASTN contact points are not accessible via this endpoint.
Args:
- id (int): Parameter id (required)
+ uuid (UUID): Parameter uuid (required)
+ Returns:
+ ContactPointResult: The response
"""
# Get path template from class attribute
- path = PATH_DELETE_WATCHLIST
+ path = PATH_GET_CONTACT_POINT
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async("DELETE", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return ContactPointResult.model_validate(resp.json())
# ----------------- get_journalist_by_id (sync) ----------------- #
def get_journalist_by_id(self, id: str) -> Journalist:
@@ -334,44 +480,112 @@ def get_journalist_by_id(self, id: str) -> Journalist:
resp.raise_for_status()
return Journalist.model_validate(resp.json())
- # ----------------- get_journalist_by_id (async) ----------------- #
- async def get_journalist_by_id_async(self, id: str) -> Journalist:
+ # ----------------- get_limits (sync) ----------------- #
+ def get_limits(self, api_keys: Optional[List[str]] = None) -> LimitsResult:
"""
- Async variant of get_journalist_by_id. Find additional details on a journalist by using the journalist ID found in an article response object.
+ Returns the authenticated account's current API usage and the limits that apply to it: the number of requests made in the current tracking period, the request limit, the per-second rate limit, and when the counter resets. Optionally, pass one or more API keys via the `apiKeys` parameter to check their status. For each supplied key the response reports whether it is `valid` (exists and belongs to your account), `enabled` (a disabled key is 'off'), and whether its `subscription` is active. This endpoint is authenticated with your API key but is quota-exempt: calling it does **not** count against your monthly request quota and is not subject to rate limiting. It is intended for liveness/health probes and external dashboards that need to check whether an API key and subscription are active without consuming usage.
Args:
- id (str): Parameter id (required)
+ api_keys (Optional[List[str]]): Optional list of API keys whose validity and status should be checked. Each key is reported as valid only if it exists and belongs to your account.
Returns:
- Journalist: The response
+ LimitsResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_JOURNALIST_BY_ID
+ path = PATH_GET_LIMITS
- # Replace path parameters
- path = path.format(id=str(id))
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if api_keys is not None:
+ params["apiKeys"] = api_keys
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return LimitsResult.model_validate(resp.json())
+
+ # ----------------- get_linkedin_company_posts (sync) ----------------- #
+ def get_linkedin_company_posts(
+ self, url: str, type: Optional[str] = None, cursor: Optional[str] = None
+ ) -> SocialLinkedInFeedResult:
+ """
+ Get the recent posts of a LinkedIn company page, newest first. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ url (str): LinkedIn company page URL, e.g. `https://www.linkedin.com/company/microsoft`. (required)
+ type (Optional[str]): Kind of posts to return. Defaults to all posts.
+ Allowed values: 'ALL', 'ARTICLES', 'DOCUMENTS'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialLinkedInFeedResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_LINKEDIN_COMPANY_POSTS
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
+ if url is not None:
+ params["url"] = url
+ if type is not None:
+ params["type"] = type
+ if cursor is not None:
+ params["cursor"] = cursor
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return Journalist.model_validate(resp.json())
+ return SocialLinkedInFeedResult.model_validate(resp.json())
- # ----------------- get_source_group (sync) ----------------- #
- def get_source_group(self, id: int):
+ # ----------------- get_linkedin_person_activity (sync) ----------------- #
+ def get_linkedin_person_activity(
+ self, url: str, type: Optional[str] = None, cursor: Optional[str] = None
+ ) -> SocialLinkedInFeedResult:
"""
- Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
+ Get the recent posts, comments or reactions of a LinkedIn member, newest first. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
Args:
- id (int): Parameter id (required)
+ url (str): LinkedIn profile URL, e.g. `https://www.linkedin.com/in/williamhgates`. (required)
+ type (Optional[str]): Kind of activity to return. Defaults to posts.
+ Allowed values: 'POSTS', 'COMMENTS', 'REACTIONS'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+ Returns:
+ SocialLinkedInFeedResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_SOURCE_GROUP
+ path = PATH_GET_LINKEDIN_PERSON_ACTIVITY
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if url is not None:
+ params["url"] = url
+ if type is not None:
+ params["type"] = type
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialLinkedInFeedResult.model_validate(resp.json())
+
+ # ----------------- get_monitor_api (sync) ----------------- #
+ def get_monitor_api(self, uuid: UUID) -> MonitorResult:
+ """
+ Retrieve a monitor by UUID for the organization associated with the API key.
+
+ Args:
+ uuid (UUID): UUID of the monitor to retrieve. (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_MONITOR_API
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
@@ -379,16 +593,18 @@ def get_source_group(self, id: int):
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return MonitorResult.model_validate(resp.json())
- # ----------------- get_source_group (async) ----------------- #
- async def get_source_group_async(self, id: int):
+ # ----------------- get_source_group (sync) ----------------- #
+ def get_source_group(self, id: int) -> SourceGroupResult:
"""
- Async variant of get_source_group. Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
+ Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
Args:
id (int): Parameter id (required)
+ Returns:
+ SourceGroupResult: The response
"""
# Get path template from class attribute
path = PATH_GET_SOURCE_GROUP
@@ -396,12 +612,13 @@ async def get_source_group_async(self, id: int):
# Replace path parameters
path = path.format(id=str(id))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return SourceGroupResult.model_validate(resp.json())
# ----------------- get_story_counts (sync) ----------------- #
def get_story_counts(
@@ -411,9 +628,6 @@ def get_story_counts(
name: Optional[str] = None,
cluster_id: Optional[List[str]] = None,
exclude_cluster_id: Optional[List[str]] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
var_from: Optional[datetime] = None,
to: Optional[datetime] = None,
initialized_from: Optional[datetime] = None,
@@ -433,6 +647,8 @@ def get_story_counts(
company_name: Optional[str] = None,
company_domain: Optional[List[str]] = None,
company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
country: Optional[List[str]] = None,
state: Optional[List[str]] = None,
city: Optional[List[str]] = None,
@@ -446,34 +662,23 @@ def get_story_counts(
neutral_sentiment_to: Optional[float] = None,
negative_sentiment_from: Optional[float] = None,
negative_sentiment_to: Optional[float] = None,
- show_story_page_info: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
show_duplicates: Optional[bool] = None,
- show_highlighting: Optional[bool] = None,
- highlight_fragment_size: Optional[int] = None,
- highlight_num_fragments: Optional[int] = None,
- highlight_pre_tag: Optional[str] = None,
- highlight_post_tag: Optional[str] = None,
- highlight_q: Optional[str] = None,
- expand_articles: Optional[bool] = None,
+ time_zone: Optional[str] = None,
) -> StatResult:
"""
- Get statistics on story counts over time intervals. Supports filtering by various story attributes and grouping by different time intervals (hour, day, week, month).
+ Count matching stories by their creation time. Results can be grouped by hour, day, week, or month and filtered by supported story attributes.
Args:
- split_by (str): String. The value for 'splitBy' must be one of the following: HOUR, DAY, WEEK, MONTH, or NONE. (required)
- Allowed values: 'HOUR', 'DAY', 'WEEK', 'MONTH', 'NONE'
+ split_by (str): Time interval used to group story counts. Accepts hour, day, week, or month and defaults to month. (required)
+ Allowed values: 'hour', 'day', 'week', 'month'
q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
- sort_by (Optional[SortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59
- initialized_from (Optional[datetime]): Date. Filter for stories created after this date. Alternative parameter for filtering by story creation date.
- initialized_to (Optional[datetime]): Date. Filter for stories created before this date. Alternative parameter for filtering by story creation date.
+ var_from (Optional[datetime]): Filter for stories created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for stories created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ initialized_from (Optional[datetime]): Filter for stories initialized on or after this date. A story is initialized when it becomes visible with generated story data.
+ initialized_to (Optional[datetime]): Filter for stories initialized on or before this date. A story is initialized when it becomes visible with generated story data.
updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
@@ -489,6 +694,8 @@ def get_story_counts(
company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes of top mentioned companies (International Securities Identification Numbers). Returns stories where companies with these ISINs appear prominently. For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes on ticker listings of top mentioned companies (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. Returns stories where those companies appear prominently. For available company entities and their listing ISINs, consult the /companies endpoint.
country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
@@ -496,22 +703,14 @@ def get_story_counts(
min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
- positive_sentiment_from (Optional[float]): Float. Filter articles with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- positive_sentiment_to (Optional[float]): Float. Filter articles with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- neutral_sentiment_from (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- neutral_sentiment_to (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- negative_sentiment_from (Optional[float]): Float. Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- negative_sentiment_to (Optional[float]): Float. Filter articles with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- show_story_page_info (Optional[bool]): Boolean.
- show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
+ positive_sentiment_from (Optional[float]): Filter stories with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ positive_sentiment_to (Optional[float]): Filter stories with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ neutral_sentiment_from (Optional[float]): Filter stories with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ neutral_sentiment_to (Optional[float]): Filter stories with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ negative_sentiment_from (Optional[float]): Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ negative_sentiment_to (Optional[float]): Filter stories with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
- show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
- highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
- highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
- highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
- highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
- highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
- expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
+ time_zone (Optional[str]): IANA time zone used to align interval buckets (e.g. America/Chicago). Defaults to UTC. Applies to intervalArticleCounts.
Returns:
StatResult: The response
@@ -529,12 +728,6 @@ def get_story_counts(
params["clusterId"] = cluster_id
if exclude_cluster_id is not None:
params["excludeClusterId"] = exclude_cluster_id
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
if var_from is not None:
params["from"] = var_from
if to is not None:
@@ -573,6 +766,10 @@ def get_story_counts(
params["companyDomain"] = company_domain
if company_symbol is not None:
params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
if country is not None:
params["country"] = country
if state is not None:
@@ -599,341 +796,192 @@ def get_story_counts(
params["negativeSentimentFrom"] = negative_sentiment_from
if negative_sentiment_to is not None:
params["negativeSentimentTo"] = negative_sentiment_to
- if show_story_page_info is not None:
- params["showStoryPageInfo"] = show_story_page_info
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
if show_duplicates is not None:
params["showDuplicates"] = show_duplicates
- if show_highlighting is not None:
- params["showHighlighting"] = show_highlighting
- if highlight_fragment_size is not None:
- params["highlightFragmentSize"] = highlight_fragment_size
- if highlight_num_fragments is not None:
- params["highlightNumFragments"] = highlight_num_fragments
- if highlight_pre_tag is not None:
- params["highlightPreTag"] = highlight_pre_tag
- if highlight_post_tag is not None:
- params["highlightPostTag"] = highlight_post_tag
- if highlight_q is not None:
- params["highlightQ"] = highlight_q
- if expand_articles is not None:
- params["expandArticles"] = expand_articles
if split_by is not None:
params["splitBy"] = split_by
+ if time_zone is not None:
+ params["timeZone"] = time_zone
params = _normalise_query(params)
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
return StatResult.model_validate(resp.json())
- # ----------------- get_story_counts (async) ----------------- #
- async def get_story_counts_async(
+ # ----------------- get_story_history (sync) ----------------- #
+ def get_story_history(
self,
- split_by: str,
- q: Optional[str] = None,
- name: Optional[str] = None,
cluster_id: Optional[List[str]] = None,
- exclude_cluster_id: Optional[List[str]] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
var_from: Optional[datetime] = None,
to: Optional[datetime] = None,
- initialized_from: Optional[datetime] = None,
- initialized_to: Optional[datetime] = None,
- updated_from: Optional[datetime] = None,
- updated_to: Optional[datetime] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- taxonomy: Optional[List[str]] = None,
- source: Optional[List[str]] = None,
- source_group: Optional[List[str]] = None,
- min_unique_sources: Optional[int] = None,
- min_source_diversity: Optional[float] = None,
- person_wikidata_id: Optional[List[str]] = None,
- person_name: Optional[str] = None,
- company_id: Optional[List[str]] = None,
- company_name: Optional[str] = None,
- company_domain: Optional[List[str]] = None,
- company_symbol: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- state: Optional[List[str]] = None,
- city: Optional[List[str]] = None,
- area: Optional[List[str]] = None,
- min_cluster_size: Optional[int] = None,
- max_cluster_size: Optional[int] = None,
- name_exists: Optional[bool] = None,
- positive_sentiment_from: Optional[float] = None,
- positive_sentiment_to: Optional[float] = None,
- neutral_sentiment_from: Optional[float] = None,
- neutral_sentiment_to: Optional[float] = None,
- negative_sentiment_from: Optional[float] = None,
- negative_sentiment_to: Optional[float] = None,
- show_story_page_info: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- show_duplicates: Optional[bool] = None,
- show_highlighting: Optional[bool] = None,
- highlight_fragment_size: Optional[int] = None,
- highlight_num_fragments: Optional[int] = None,
- highlight_pre_tag: Optional[str] = None,
- highlight_post_tag: Optional[str] = None,
- highlight_q: Optional[str] = None,
- expand_articles: Optional[bool] = None,
- ) -> StatResult:
+ sort_by: Optional[str] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ changelog_exists: Optional[bool] = None,
+ ) -> StoryHistoryResult:
"""
- Async variant of get_story_counts. Get statistics on story counts over time intervals. Supports filtering by various story attributes and grouping by different time intervals (hour, day, week, month).
+ Retrieve previous generated versions of stories and their evolving summaries, key points, questions, and changelogs.
Args:
- split_by (str): String. The value for 'splitBy' must be one of the following: HOUR, DAY, WEEK, MONTH, or NONE. (required)
- Allowed values: 'HOUR', 'DAY', 'WEEK', 'MONTH', 'NONE'
- q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
- cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
- exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
- sort_by (Optional[SortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59
- initialized_from (Optional[datetime]): Date. Filter for stories created after this date. Alternative parameter for filtering by story creation date.
- initialized_to (Optional[datetime]): Date. Filter for stories created before this date. Alternative parameter for filtering by story creation date.
- updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
- updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
- topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
- category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
- taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
- source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
- source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
- min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
- min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
- person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
- person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
- company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
- company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
- company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
- company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
- country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
- state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
- max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
- name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
- positive_sentiment_from (Optional[float]): Float. Filter articles with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- positive_sentiment_to (Optional[float]): Float. Filter articles with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- neutral_sentiment_from (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- neutral_sentiment_to (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- negative_sentiment_from (Optional[float]): Float. Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- negative_sentiment_to (Optional[float]): Float. Filter articles with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- show_story_page_info (Optional[bool]): Boolean.
- show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
- show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
- show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
- highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
- highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
- highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
- highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
- highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
- expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
+ cluster_id (Optional[List[str]]): Filter history records by story cluster ID. Multiple values are combined with OR.
+ var_from (Optional[datetime]): Filter for history records created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for history records created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ sort_by (Optional[str]): Field used to sort history records in descending order. Accepts createdAt or triggeredAt and defaults to createdAt.
+ Allowed values: 'createdAt', 'triggeredAt'
+ page (Optional[int]): Integer. Zero-based page number. From 0 to 10000. See the Pagination section for limitations.
+ size (Optional[int]): Integer. Number of stories results per page, from 0 to 100.
+ changelog_exists (Optional[bool]): Filter by whether a history record contains a changelog. Use true to require one or false to require its absence.
Returns:
- StatResult: The response
+ StoryHistoryResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_STORY_COUNTS
+ path = PATH_GET_STORY_HISTORY
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
if cluster_id is not None:
params["clusterId"] = cluster_id
- if exclude_cluster_id is not None:
- params["excludeClusterId"] = exclude_cluster_id
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
if sort_by is not None:
params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if var_from is not None:
- params["from"] = var_from
- if to is not None:
- params["to"] = to
- if initialized_from is not None:
- params["initializedFrom"] = initialized_from
- if initialized_to is not None:
- params["initializedTo"] = initialized_to
- if updated_from is not None:
- params["updatedFrom"] = updated_from
- if updated_to is not None:
- params["updatedTo"] = updated_to
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if taxonomy is not None:
- params["taxonomy"] = taxonomy
- if source is not None:
- params["source"] = source
- if source_group is not None:
- params["sourceGroup"] = source_group
- if min_unique_sources is not None:
- params["minUniqueSources"] = min_unique_sources
- if min_source_diversity is not None:
- params["minSourceDiversity"] = min_source_diversity
- if person_wikidata_id is not None:
- params["personWikidataId"] = person_wikidata_id
- if person_name is not None:
- params["personName"] = person_name
- if company_id is not None:
- params["companyId"] = company_id
- if company_name is not None:
- params["companyName"] = company_name
- if company_domain is not None:
- params["companyDomain"] = company_domain
- if company_symbol is not None:
- params["companySymbol"] = company_symbol
- if country is not None:
- params["country"] = country
- if state is not None:
- params["state"] = state
- if city is not None:
- params["city"] = city
- if area is not None:
- params["area"] = area
- if min_cluster_size is not None:
- params["minClusterSize"] = min_cluster_size
- if max_cluster_size is not None:
- params["maxClusterSize"] = max_cluster_size
- if name_exists is not None:
- params["nameExists"] = name_exists
- if positive_sentiment_from is not None:
- params["positiveSentimentFrom"] = positive_sentiment_from
- if positive_sentiment_to is not None:
- params["positiveSentimentTo"] = positive_sentiment_to
- if neutral_sentiment_from is not None:
- params["neutralSentimentFrom"] = neutral_sentiment_from
- if neutral_sentiment_to is not None:
- params["neutralSentimentTo"] = neutral_sentiment_to
- if negative_sentiment_from is not None:
- params["negativeSentimentFrom"] = negative_sentiment_from
- if negative_sentiment_to is not None:
- params["negativeSentimentTo"] = negative_sentiment_to
- if show_story_page_info is not None:
- params["showStoryPageInfo"] = show_story_page_info
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- if show_duplicates is not None:
- params["showDuplicates"] = show_duplicates
- if show_highlighting is not None:
- params["showHighlighting"] = show_highlighting
- if highlight_fragment_size is not None:
- params["highlightFragmentSize"] = highlight_fragment_size
- if highlight_num_fragments is not None:
- params["highlightNumFragments"] = highlight_num_fragments
- if highlight_pre_tag is not None:
- params["highlightPreTag"] = highlight_pre_tag
- if highlight_post_tag is not None:
- params["highlightPostTag"] = highlight_post_tag
- if highlight_q is not None:
- params["highlightQ"] = highlight_q
- if expand_articles is not None:
- params["expandArticles"] = expand_articles
- if split_by is not None:
- params["splitBy"] = split_by
+ if changelog_exists is not None:
+ params["changelogExists"] = changelog_exists
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return StatResult.model_validate(resp.json())
+ return StoryHistoryResult.model_validate(resp.json())
- # ----------------- get_story_history (sync) ----------------- #
- def get_story_history(
+ # ----------------- get_watchlist (sync) ----------------- #
+ def get_watchlist(self, id: int) -> WatchlistResult:
+ """
+ Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
+
+ Args:
+ id (int): Parameter id (required)
+
+ Returns:
+ WatchlistResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_WATCHLIST
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return WatchlistResult.model_validate(resp.json())
+
+ # ----------------- list_contact_points (sync) ----------------- #
+ def list_contact_points(
self,
- cluster_id: Optional[List[str]] = None,
- var_from: Optional[datetime] = None,
- to: Optional[datetime] = None,
- sort_by: Optional[str] = None,
+ sort_by: ContactPointSortBy,
+ sort_order: str,
+ status: Optional[List[str]] = None,
+ type: Optional[List[str]] = None,
page: Optional[int] = None,
size: Optional[int] = None,
- changelog_exists: Optional[bool] = None,
- ) -> StoryHistoryResult:
+ nulls: Optional[str] = None,
+ ) -> ContactPointListResult:
"""
+ List contact points owned by the organization associated with the API key. Supports filtering by status and type. FASTN contact points are excluded from results.
Args:
- cluster_id (Optional[List[str]]): String Array. Filter to specific story. Passing a cluster ID will filter results to only the content found within the cluster.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59.
- sort_by (Optional[str]): String. Sort stories by count, creation date (createdAt), story refresh trigger date (triggeredAt).
- Allowed values: 'createdAt', 'triggeredAt'
- page (Optional[int]): Integer. Zero-based page number. From 0 to 10000. See the Pagination section for limitations.
- size (Optional[int]): Integer. Number of stories results per page, from 0 to 100.
- changelog_exists (Optional[bool]): Boolean. Filter to only include clusters that have a changelog or not.
+ sort_by (ContactPointSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ status (Optional[List[str]]): Parameter status
+ Allowed values: 'ACTIVE', 'STOPPED', 'FAILING', 'ARCHIVED'
+ type (Optional[List[str]]): Parameter type
+ Allowed values: 'EMAIL', 'FASTN', 'WEBHOOK'
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- StoryHistoryResult: The response
+ ContactPointListResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_STORY_HISTORY
+ path = PATH_LIST_CONTACT_POINTS
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if cluster_id is not None:
- params["clusterId"] = cluster_id
- if var_from is not None:
- params["from"] = var_from
- if to is not None:
- params["to"] = to
+ if status is not None:
+ params["status"] = status
+ if type is not None:
+ params["type"] = type
if sort_by is not None:
params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if changelog_exists is not None:
- params["changelogExists"] = changelog_exists
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return StoryHistoryResult.model_validate(resp.json())
+ return ContactPointListResult.model_validate(resp.json())
- # ----------------- get_story_history (async) ----------------- #
- async def get_story_history_async(
+ # ----------------- list_monitor_events_api (sync) ----------------- #
+ def list_monitor_events_api(
self,
- cluster_id: Optional[List[str]] = None,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ event_type: Optional[str] = None,
var_from: Optional[datetime] = None,
to: Optional[datetime] = None,
- sort_by: Optional[str] = None,
page: Optional[int] = None,
size: Optional[int] = None,
- changelog_exists: Optional[bool] = None,
- ) -> StoryHistoryResult:
+ nulls: Optional[str] = None,
+ ) -> MonitorEventListResult:
"""
- Async variant of get_story_history.
+ List structured events emitted by a monitor. Events represent individual matches detected by the monitor (e.g. a news article matching the monitor's criteria) and are produced asynchronously as new content is ingested.
Args:
- cluster_id (Optional[List[str]]): String Array. Filter to specific story. Passing a cluster ID will filter results to only the content found within the cluster.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59.
- sort_by (Optional[str]): String. Sort stories by count, creation date (createdAt), story refresh trigger date (triggeredAt).
- Allowed values: 'createdAt', 'triggeredAt'
- page (Optional[int]): Integer. Zero-based page number. From 0 to 10000. See the Pagination section for limitations.
- size (Optional[int]): Integer. Number of stories results per page, from 0 to 100.
- changelog_exists (Optional[bool]): Boolean. Filter to only include clusters that have a changelog or not.
+ uuid (UUID): UUID of the monitor whose events to retrieve. (required)
+ sort_by (str): Required field used to sort events. Accepts createdAt, updatedAt, or eventDate. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ event_type (Optional[str]): Filter for events with this exact event type.
+ var_from (Optional[datetime]): Filter for events created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for events created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- StoryHistoryResult: The response
+ MonitorEventListResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_STORY_HISTORY
+ path = PATH_LIST_MONITOR_EVENTS_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if cluster_id is not None:
- params["clusterId"] = cluster_id
+ if event_type is not None:
+ params["eventType"] = event_type
if var_from is not None:
params["from"] = var_from
if to is not None:
@@ -944,167 +992,277 @@ async def get_story_history_async(
params["page"] = page
if size is not None:
params["size"] = size
- if changelog_exists is not None:
- params["changelogExists"] = changelog_exists
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return StoryHistoryResult.model_validate(resp.json())
+ return MonitorEventListResult.model_validate(resp.json())
- # ----------------- get_watchlist (sync) ----------------- #
- def get_watchlist(self, id: int):
+ # ----------------- list_monitor_newsletters_api (sync) ----------------- #
+ def list_monitor_newsletters_api(
+ self,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ title: Optional[str] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorNewsletterListResult:
"""
- Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
+ List newsletters generated for a monitor. Newsletters bundle a monitor's recent events into a human-readable digest and are produced on the monitor's configured schedule.
Args:
- id (int): Parameter id (required)
+ uuid (UUID): UUID of the monitor whose newsletters to retrieve. (required)
+ sort_by (str): Required field used to sort newsletters. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ title (Optional[str]): Filter by newsletter title using a case-insensitive partial match.
+ var_from (Optional[datetime]): Filter for newsletters created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for newsletters created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+ Returns:
+ MonitorNewsletterListResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_WATCHLIST
+ path = PATH_LIST_MONITOR_NEWSLETTERS_API
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
+ if title is not None:
+ params["title"] = title
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return MonitorNewsletterListResult.model_validate(resp.json())
- # ----------------- get_watchlist (async) ----------------- #
- async def get_watchlist_async(self, id: int):
+ # ----------------- list_monitor_summaries_api (sync) ----------------- #
+ def list_monitor_summaries_api(
+ self,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorSummaryListResult:
"""
- Async variant of get_watchlist. Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
+ List AI-generated summaries produced for a monitor as matching content is processed.
Args:
- id (int): Parameter id (required)
+ uuid (UUID): UUID of the monitor whose summaries to retrieve. (required)
+ sort_by (str): Required field used to sort summaries. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ var_from (Optional[datetime]): Filter for summaries created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for summaries created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+ Returns:
+ MonitorSummaryListResult: The response
"""
# Get path template from class attribute
- path = PATH_GET_WATCHLIST
+ path = PATH_LIST_MONITOR_SUMMARIES_API
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return MonitorSummaryListResult.model_validate(resp.json())
- # ----------------- list_source_groups (sync) ----------------- #
- def list_source_groups(
+ # ----------------- list_monitors_api (sync) ----------------- #
+ def list_monitors_api(
self,
sort_by: str,
sort_order: str,
+ uuid: Optional[List[UUID]] = None,
name: Optional[str] = None,
- domain: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
+ status: Optional[List[str]] = None,
+ classification_type: Optional[List[str]] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorListResult:
"""
- List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
+ List AI monitors for the organization associated with the API key. Excludes archived monitors by default. Use the status parameter to filter.
Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Parameter name
- domain (Optional[str]): Parameter domain
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
+ sort_by (str): Required field used to sort monitors. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ uuid (Optional[List[UUID]]): Filter by one or more monitor UUIDs. Multiple values are combined with OR.
+ name (Optional[str]): Filter by monitor name using a case-insensitive partial match.
+ status (Optional[List[str]]): Filter by status: DRAFT, ACTIVE, STOPPED, or ARCHIVED. When omitted, archived monitors are excluded.
+ Allowed values: 'DRAFT', 'ACTIVE', 'STOPPED', 'ARCHIVED'
+ classification_type (Optional[List[str]]): Filter by classification type: EVENT, MENTIONS, or TOPIC. Multiple values are combined with OR.
+ Allowed values: 'EVENT', 'MENTIONS', 'TOPIC'
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+ Returns:
+ MonitorListResult: The response
"""
# Get path template from class attribute
- path = PATH_LIST_SOURCE_GROUPS
+ path = PATH_LIST_MONITORS_API
# --- build query dict on the fly ---
params: Dict[str, Any] = {}
+ if uuid is not None:
+ params["uuid"] = uuid
if name is not None:
params["name"] = name
- if domain is not None:
- params["domain"] = domain
+ if status is not None:
+ params["status"] = status
+ if classification_type is not None:
+ params["classificationType"] = classification_type
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
if sort_order is not None:
params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return MonitorListResult.model_validate(resp.json())
- # ----------------- list_source_groups (async) ----------------- #
- async def list_source_groups_async(
+ # ----------------- list_source_groups (sync) ----------------- #
+ def list_source_groups(
self,
- sort_by: str,
+ sort_by: SourceGroupSortBy,
sort_order: str,
name: Optional[str] = None,
domain: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> SourceGroupListResult:
"""
- Async variant of list_source_groups. List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
+ List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
+ sort_by (SourceGroupSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
name (Optional[str]): Parameter name
domain (Optional[str]): Parameter domain
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+ Returns:
+ SourceGroupListResult: The response
"""
# Get path template from class attribute
path = PATH_LIST_SOURCE_GROUPS
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
if name is not None:
params["name"] = name
if domain is not None:
params["domain"] = domain
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
if sort_order is not None:
params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return SourceGroupListResult.model_validate(resp.json())
# ----------------- list_watchlists (sync) ----------------- #
def list_watchlists(
self,
- sort_by: str,
+ sort_by: WatchlistSortBy,
sort_order: str,
name: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> WatchlistListResult:
"""
List watchlists owned by the organization associated with the API key, as well as publicly visible watchlists. Supports filtering by name.
Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
+ sort_by (WatchlistSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
name (Optional[str]): Filter watchlists by name (case-insensitive, partial match)
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+ Returns:
+ WatchlistListResult: The response
"""
# Get path template from class attribute
path = PATH_LIST_WATCHLISTS
@@ -1113,68 +1271,59 @@ def list_watchlists(
params: Dict[str, Any] = {}
if name is not None:
params["name"] = name
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
if sort_order is not None:
params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return WatchlistListResult.model_validate(resp.json())
- # ----------------- list_watchlists (async) ----------------- #
- async def list_watchlists_async(
- self,
- sort_by: str,
- sort_order: str,
- name: Optional[str] = None,
- page: Optional[str] = None,
- size: Optional[str] = None,
- ):
+ # ----------------- pause_monitor_api (sync) ----------------- #
+ def pause_monitor_api(self, uuid: UUID) -> MonitorResult:
"""
- Async variant of list_watchlists. List watchlists owned by the organization associated with the API key, as well as publicly visible watchlists. Supports filtering by name.
+ Pause a monitor, stopping its processing pipeline.
Args:
- sort_by (str): Field to sort by. (required)
- sort_order (str): The sort order for the results. _Available values: 'asc' or 'desc'_. (required)
- name (Optional[str]): Filter watchlists by name (case-insensitive, partial match)
- page (Optional[str]): The page number to retrieve. _Starting from 0_. _Default value 0_.
- size (Optional[str]): The number of items per page. _Must be at least 1_. _Default value 10_.
+ uuid (UUID): UUID of the monitor to pause. (required)
+ Returns:
+ MonitorResult: The response
"""
# Get path template from class attribute
- path = PATH_LIST_WATCHLISTS
+ path = PATH_PAUSE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+ # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
- if sort_order is not None:
- params["sortOrder"] = sort_order
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = self.api_client.request("POST", path, params=params)
resp.raise_for_status()
- return resp.json()
+ return MonitorResult.model_validate(resp.json())
# ----------------- resolve_source_groups (sync) ----------------- #
- def resolve_source_groups(self, name: Optional[List[str]] = None):
+ def resolve_source_groups(
+ self, name: Optional[List[str]] = None
+ ) -> SourceGroupResolveResult:
"""
Resolve source groups by name. For each name, returns the organization's private source group if one exists, otherwise falls back to the matching public source group.
Args:
name (Optional[List[str]]): Source group names to resolve (max 100)
+ Returns:
+ SourceGroupResolveResult: The response
"""
# Get path template from class attribute
path = PATH_RESOLVE_SOURCE_GROUPS
@@ -1187,37 +1336,20 @@ def resolve_source_groups(self, name: Optional[List[str]] = None):
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_source_groups (async) ----------------- #
- async def resolve_source_groups_async(self, name: Optional[List[str]] = None):
- """
- Async variant of resolve_source_groups. Resolve source groups by name. For each name, returns the organization's private source group if one exists, otherwise falls back to the matching public source group.
-
- Args:
- name (Optional[List[str]]): Source group names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_SOURCE_GROUPS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
+ return SourceGroupResolveResult.model_validate(resp.json())
# ----------------- resolve_watchlists (sync) ----------------- #
- def resolve_watchlists(self, name: Optional[List[str]] = None):
+ def resolve_watchlists(
+ self, name: Optional[List[str]] = None
+ ) -> WatchlistResolveResult:
"""
Resolve watchlists by name. For each name, returns the organization's private watchlist if one exists, otherwise falls back to the matching public watchlist.
Args:
name (Optional[List[str]]): Watchlist names to resolve (max 100)
+ Returns:
+ WatchlistResolveResult: The response
"""
# Get path template from class attribute
path = PATH_RESOLVE_WATCHLISTS
@@ -1230,28 +1362,7 @@ def resolve_watchlists(self, name: Optional[List[str]] = None):
resp = self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return resp.json()
-
- # ----------------- resolve_watchlists (async) ----------------- #
- async def resolve_watchlists_async(self, name: Optional[List[str]] = None):
- """
- Async variant of resolve_watchlists. Resolve watchlists by name. For each name, returns the organization's private watchlist if one exists, otherwise falls back to the matching public watchlist.
-
- Args:
- name (Optional[List[str]]): Watchlist names to resolve (max 100)
-
- """
- # Get path template from class attribute
- path = PATH_RESOLVE_WATCHLISTS
-
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- params = _normalise_query(params)
-
- resp = await self.api_client.request_async("GET", path, params=params)
- resp.raise_for_status()
- return resp.json()
+ return WatchlistResolveResult.model_validate(resp.json())
# ----------------- search_articles (sync) ----------------- #
def search_articles(
@@ -1333,6 +1444,10 @@ def search_articles(
company_domain: Optional[List[str]] = None,
exclude_company_domain: Optional[List[str]] = None,
company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_isin: Optional[List[str]] = None,
+ exclude_company_symbol_isin: Optional[List[str]] = None,
exclude_company_symbol: Optional[List[str]] = None,
show_num_results: Optional[bool] = None,
positive_sentiment_from: Optional[float] = None,
@@ -1419,7 +1534,7 @@ def search_articles(
source_state (Optional[List[str]]): String Array. Find articles published by sources that are located within a given state.
exclude_source_state (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified states.
source_lat (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
- source_lon (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
+ source_lon (Optional[float]): Float. Longitude of the center point to search articles created by local publications.
source_max_distance (Optional[float]): Float. Maximum distance from starting point to search articles created by local publications.
person_wikidata_id (Optional[List[str]]): String Array. Filter articles by Wikidata IDs of mentioned people. Refer to the /people endpoint for a complete list of tracked individuals.
exclude_person_wikidata_id (Optional[List[str]]): String Array. Exclude articles mentioning people with specific Wikidata IDs. Creates an AND-exclude filter to remove content about these individuals. Uses precise identifiers to avoid name ambiguity.
@@ -1431,6 +1546,10 @@ def search_articles(
company_domain (Optional[List[str]]): String Array. Filter articles by company domains (e.g., apple.com). For available company entities, consult the /companies endpoint.
exclude_company_domain (Optional[List[str]]): String Array. Exclude articles related to companies with specific domains. Creates an AND-exclude filter to remove content about these companies.
company_symbol (Optional[List[str]]): String Array. Filter articles by company stock symbols. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter articles by company ISIN codes (International Securities Identification Numbers). For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter articles by ISIN codes on company ticker listings (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
+ exclude_company_isin (Optional[List[str]]): String Array. Exclude articles related to companies with specific ISIN codes. Creates an AND-exclude filter to remove content about these companies. For available company entities and their ISINs, consult the /companies endpoint.
+ exclude_company_symbol_isin (Optional[List[str]]): String Array. Exclude articles related to companies whose ticker listings have these ISIN codes (symbols.isin). Distinct from excludeCompanyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
exclude_company_symbol (Optional[List[str]]): String Array. A list of stock symbols (ticker symbols) that identify companies to be excluded. Articles related to companies using any of these symbols will be omitted, which is useful for targeting or avoiding specific public companies.
show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.
positive_sentiment_from (Optional[float]): Float. Filter articles with a positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
@@ -1610,6 +1729,14 @@ def search_articles(
params["excludeCompanyDomain"] = exclude_company_domain
if company_symbol is not None:
params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if exclude_company_isin is not None:
+ params["excludeCompanyIsin"] = exclude_company_isin
+ if exclude_company_symbol_isin is not None:
+ params["excludeCompanySymbolIsin"] = exclude_company_symbol_isin
if exclude_company_symbol is not None:
params["excludeCompanySymbol"] = exclude_company_symbol
if show_num_results is not None:
@@ -1648,130 +1775,819 @@ def search_articles(
resp.raise_for_status()
return QuerySearchResult.model_validate(resp.json())
- # ----------------- search_articles (async) ----------------- #
- async def search_articles_async(
+ # ----------------- search_companies (sync) ----------------- #
+ def search_companies(
self,
+ id: Optional[List[str]] = None,
+ symbol: Optional[List[str]] = None,
+ symbol_isin: Optional[List[str]] = None,
+ domain: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ exchange: Optional[List[str]] = None,
+ num_employees_from: Optional[int] = None,
+ num_employees_to: Optional[int] = None,
+ ipo_from: Optional[datetime] = None,
+ ipo_to: Optional[datetime] = None,
q: Optional[str] = None,
- title: Optional[str] = None,
- desc: Optional[str] = None,
- content: Optional[str] = None,
- summary: Optional[str] = None,
- url: Optional[str] = None,
- article_id: Optional[List[str]] = None,
- cluster_id: Optional[List[str]] = None,
- sort_by: Optional[AllEndpointSortBy] = None,
- page: Optional[int] = None,
+ name: Optional[str] = None,
+ industry: Optional[str] = None,
+ sector: Optional[str] = None,
size: Optional[int] = None,
- var_from: Optional[datetime] = None,
- to: Optional[datetime] = None,
- add_date_from: Optional[datetime] = None,
- add_date_to: Optional[datetime] = None,
- refresh_date_from: Optional[datetime] = None,
- refresh_date_to: Optional[datetime] = None,
- medium: Optional[List[str]] = None,
- source: Optional[List[str]] = None,
- source_group: Optional[List[str]] = None,
- exclude_source_group: Optional[List[str]] = None,
- exclude_source: Optional[List[str]] = None,
- watchlist: Optional[List[str]] = None,
- exclude_watchlist: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- byline: Optional[List[str]] = None,
- author: Optional[List[str]] = None,
- exclude_author: Optional[List[str]] = None,
- journalist_id: Optional[List[str]] = None,
- exclude_journalist_id: Optional[List[str]] = None,
- language: Optional[List[str]] = None,
- exclude_language: Optional[List[str]] = None,
- search_translation: Optional[bool] = None,
- label: Optional[List[str]] = None,
- exclude_label: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- exclude_category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- exclude_topic: Optional[List[str]] = None,
- link_to: Optional[str] = None,
- show_reprints: Optional[bool] = None,
- reprint_group_id: Optional[str] = None,
- city: Optional[List[str]] = None,
- exclude_city: Optional[List[str]] = None,
- area: Optional[List[str]] = None,
- state: Optional[List[str]] = None,
- exclude_state: Optional[List[str]] = None,
- county: Optional[List[str]] = None,
- exclude_county: Optional[List[str]] = None,
- locations_country: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exclude_locations_country: Optional[List[str]] = None,
- location: Optional[List[str]] = None,
- lat: Optional[float] = None,
- lon: Optional[float] = None,
- max_distance: Optional[float] = None,
- source_city: Optional[List[str]] = None,
- exclude_source_city: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- exclude_source_county: Optional[List[str]] = None,
- source_country: Optional[List[str]] = None,
- exclude_source_country: Optional[List[str]] = None,
- source_state: Optional[List[str]] = None,
- exclude_source_state: Optional[List[str]] = None,
- source_lat: Optional[float] = None,
- source_lon: Optional[float] = None,
- source_max_distance: Optional[float] = None,
- person_wikidata_id: Optional[List[str]] = None,
- exclude_person_wikidata_id: Optional[List[str]] = None,
- person_name: Optional[List[str]] = None,
- exclude_person_name: Optional[List[str]] = None,
- company_id: Optional[List[str]] = None,
- exclude_company_id: Optional[List[str]] = None,
- company_name: Optional[str] = None,
- company_domain: Optional[List[str]] = None,
- exclude_company_domain: Optional[List[str]] = None,
- company_symbol: Optional[List[str]] = None,
- exclude_company_symbol: Optional[List[str]] = None,
- show_num_results: Optional[bool] = None,
- positive_sentiment_from: Optional[float] = None,
- positive_sentiment_to: Optional[float] = None,
- neutral_sentiment_from: Optional[float] = None,
- neutral_sentiment_to: Optional[float] = None,
- negative_sentiment_from: Optional[float] = None,
- negative_sentiment_to: Optional[float] = None,
- taxonomy: Optional[List[str]] = None,
- prefix_taxonomy: Optional[str] = None,
- show_highlighting: Optional[bool] = None,
- highlight_fragment_size: Optional[int] = None,
- highlight_num_fragments: Optional[int] = None,
- highlight_pre_tag: Optional[str] = None,
- highlight_post_tag: Optional[str] = None,
- highlight_q: Optional[str] = None,
- ) -> QuerySearchResult:
+ page: Optional[int] = None,
+ ) -> CompanySearchResult:
"""
- Async variant of search_articles. Search and filter all news articles available via the Perigon API. The result includes a list of individual articles that were matched to your specific criteria.
+ Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
Args:
- q (Optional[str]): String. Primary search query for filtering articles based on their title, description, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- title (Optional[str]): String. Search specifically within article headlines/titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- desc (Optional[str]): String. Search within article description fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
- content (Optional[str]): String. Search within the full article body content. Supports Boolean logic, exact phrase matching with quotes, and wildcards for comprehensive content searching.
- summary (Optional[str]): String. Search within article summary fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
- url (Optional[str]): String. Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel.
- article_id (Optional[List[str]]): String Array. Retrieve specific news articles by their unique article identifiers. Multiple IDs can be provided to return a collection of specific articles.
- cluster_id (Optional[List[str]]): String Array. Filter results to only show content within a specific related content cluster. Returns articles grouped together as part of Perigon Stories based on topic relevance.
- sort_by (Optional[AllEndpointSortBy]): String. Determines the article sorting order. Options include relevance (default), date/pubDate (newest publication date first), reverseDate (oldest publication date first), addDate (newest ingestion date first), reverseAddDate (oldest ingestion date first), and refreshDate (most recently updated in system first, often identical to addDate).
+ id (Optional[List[str]]): String Array. Filter by unique company identifiers. Multiple values create an OR filter.
+ symbol (Optional[List[str]]): String Array. Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
+ symbol_isin (Optional[List[str]]): String Array. Filter by ISIN codes on company ticker listings. Matches symbols.isin rather than the company-level ISIN. Multiple values create an OR filter.
+ domain (Optional[List[str]]): String Array. Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
+ country (Optional[List[str]]): String Array. Filter by company headquarters country. Multiple values create an OR filter.
+ exchange (Optional[List[str]]): String Array. Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
+ num_employees_from (Optional[int]): Integer. Filter for companies with at least this many employees.
+ num_employees_to (Optional[int]): Integer. Filter for companies with no more than this many employees.
+ ipo_from (Optional[datetime]): Date. Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
+ ipo_to (Optional[datetime]): Date. Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
+ q (Optional[str]): String. Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ industry (Optional[str]): String. Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ sector (Optional[str]): String. Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ size (Optional[int]): Integer. The number of companies to return per page in the paginated response.
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. Filter for articles published after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- to (Optional[datetime]): Date. Filter for articles published before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- add_date_from (Optional[datetime]): Date. Filter for articles added to Perigon's system after this date. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
- add_date_to (Optional[datetime]): Date. Filter for articles added to Perigon's system before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- refresh_date_from (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system after this date. In most cases yields similar results to addDateFrom but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
- refresh_date_to (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system before this date. In most cases yields similar results to addDateTo but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- medium (Optional[List[str]]): String Array. Filter articles by their primary medium type. Accepts Article for written content or Video for video-based stories. Multiple values create an OR filter.
- source (Optional[List[str]]): String Array. Filter articles by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- source_group (Optional[List[str]]): String Array. Filter articles using Perigon's curated publisher bundles (e.g., top100, top25crypto). Multiple values create an OR filter to include articles from any of the specified bundles.
- exclude_source_group (Optional[List[str]]): String Array. Exclude articles from specified Perigon source groups. Multiple values create an AND-exclude filter, removing content from publishers in any of the specified bundles (e.g., top10, top100).
- exclude_source (Optional[List[str]]): String Array. Exclude articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an AND-exclude filter.
- watchlist (Optional[List[str]]): String Array. Filter articles using watchlists of people and companies. Multiple values create an OR filter to include articles mentioning any entity from the specified watchlists.
+
+ Returns:
+ CompanySearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_COMPANIES
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if id is not None:
+ params["id"] = id
+ if symbol is not None:
+ params["symbol"] = symbol
+ if symbol_isin is not None:
+ params["symbolIsin"] = symbol_isin
+ if domain is not None:
+ params["domain"] = domain
+ if country is not None:
+ params["country"] = country
+ if exchange is not None:
+ params["exchange"] = exchange
+ if num_employees_from is not None:
+ params["numEmployeesFrom"] = num_employees_from
+ if num_employees_to is not None:
+ params["numEmployeesTo"] = num_employees_to
+ if ipo_from is not None:
+ params["ipoFrom"] = ipo_from
+ if ipo_to is not None:
+ params["ipoTo"] = ipo_to
+ if q is not None:
+ params["q"] = q
+ if name is not None:
+ params["name"] = name
+ if industry is not None:
+ params["industry"] = industry
+ if sector is not None:
+ params["sector"] = sector
+ if size is not None:
+ params["size"] = size
+ if page is not None:
+ params["page"] = page
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return CompanySearchResult.model_validate(resp.json())
+
+ # ----------------- search_journalists (sync) ----------------- #
+ def search_journalists(
+ self,
+ id: Optional[List[str]] = None,
+ q: Optional[str] = None,
+ name: Optional[str] = None,
+ twitter: Optional[str] = None,
+ sort_by: Optional[JournalistsSortBy] = None,
+ size: Optional[int] = None,
+ page: Optional[int] = None,
+ source: Optional[List[str]] = None,
+ topic: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ label: Optional[List[str]] = None,
+ min_monthly_posts: Optional[int] = None,
+ max_monthly_posts: Optional[int] = None,
+ country: Optional[List[str]] = None,
+ location_country: Optional[List[str]] = None,
+ location_state: Optional[List[str]] = None,
+ location_county: Optional[List[str]] = None,
+ location_city: Optional[List[str]] = None,
+ location_area: Optional[List[str]] = None,
+ updated_at_from: Optional[datetime] = None,
+ updated_at_to: Optional[datetime] = None,
+ show_num_results: Optional[bool] = None,
+ ) -> JournalistSearchResult:
+ """
+ Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
+
+ Args:
+ id (Optional[List[str]]): String Array. Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
+ q (Optional[str]): String. Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ twitter (Optional[str]): String. Filter journalists by their exact Twitter handle, without the @ symbol.
+ sort_by (Optional[JournalistsSortBy]): String. Sort journalists from highest to lowest by avgMonthlyPosts, linkedinConnections, or linkedinFollowers. When omitted, results are sorted by relevance.
+ size (Optional[int]): Integer. The number of journalists to return per page in the paginated response.
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ source (Optional[List[str]]): String Array. Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
+ topic (Optional[List[str]]): String Array. Filter journalists by the top topics they cover. Topics are more specific themes that the article is about. Examples of topics: 'Economy', 'Real Estate', 'Cryptocurrency'. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
+ category (Optional[List[str]]): String Array. Filter journalists by the top categories they cover. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
+ label (Optional[List[str]]): String Array. Filter journalists by the most common label tagged to the articles they publish. This accepts labels like 'Opinion' or 'Pop Culture'. (Searches the topLabels data field.)
+ min_monthly_posts (Optional[int]): Integer. Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
+ max_monthly_posts (Optional[int]): Integer. Filter for journalists who publish no more than this many articles per month.
+ country (Optional[List[str]]): String Array. Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
+ location_country (Optional[List[str]]): String Array. Filter journalists by their profile location country (`locations.country`). Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). `uk` is accepted as an alias for `gb`. Distinct from `country`, which filters reporting focus (`topCountries`). Multiple values create an OR filter.
+ location_state (Optional[List[str]]): String Array. Filter journalists by their profile location state or region (`locations.state`). For US locations this is the two-letter state code (e.g., NY, CA). Multiple values create an OR filter.
+ location_county (Optional[List[str]]): String Array. Filter journalists by their profile location county (`locations.county`). County names should match stored metadata (e.g., 'Los Angeles County', 'Cook County'). Multiple values create an OR filter.
+ location_city (Optional[List[str]]): String Array. Filter journalists by their profile location city (`locations.city`). Multiple values create an OR filter.
+ location_area (Optional[List[str]]): String Array. Filter journalists by their profile location area β neighborhood, borough, or district (`locations.area`). Multiple values create an OR filter.
+ updated_at_from (Optional[datetime]): Date. Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ updated_at_to (Optional[datetime]): Date. Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
+ show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+
+ Returns:
+ JournalistSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_JOURNALISTS
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if id is not None:
+ params["id"] = id
+ if q is not None:
+ params["q"] = q
+ if name is not None:
+ params["name"] = name
+ if twitter is not None:
+ params["twitter"] = twitter
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if size is not None:
+ params["size"] = size
+ if page is not None:
+ params["page"] = page
+ if source is not None:
+ params["source"] = source
+ if topic is not None:
+ params["topic"] = topic
+ if category is not None:
+ params["category"] = category
+ if label is not None:
+ params["label"] = label
+ if min_monthly_posts is not None:
+ params["minMonthlyPosts"] = min_monthly_posts
+ if max_monthly_posts is not None:
+ params["maxMonthlyPosts"] = max_monthly_posts
+ if country is not None:
+ params["country"] = country
+ if location_country is not None:
+ params["locationCountry"] = location_country
+ if location_state is not None:
+ params["locationState"] = location_state
+ if location_county is not None:
+ params["locationCounty"] = location_county
+ if location_city is not None:
+ params["locationCity"] = location_city
+ if location_area is not None:
+ params["locationArea"] = location_area
+ if updated_at_from is not None:
+ params["updatedAtFrom"] = updated_at_from
+ if updated_at_to is not None:
+ params["updatedAtTo"] = updated_at_to
+ if show_num_results is not None:
+ params["showNumResults"] = show_num_results
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return JournalistSearchResult.model_validate(resp.json())
+
+ # ----------------- search_linkedin_posts (sync) ----------------- #
+ def search_linkedin_posts(
+ self,
+ q: str,
+ sort_by: Optional[str] = None,
+ date_posted: Optional[str] = None,
+ content_type: Optional[str] = None,
+ author_title: Optional[str] = None,
+ page: Optional[int] = None,
+ ) -> SocialLinkedInPostSearchResult:
+ """
+ Search public LinkedIn posts by keyword. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ q (str): Search keywords. (required)
+ sort_by (Optional[str]): Sort order of the results.
+ Allowed values: 'DATE_POSTED', 'RELEVANCE'
+ date_posted (Optional[str]): Only return posts published within this time window.
+ Allowed values: 'PAST_24H', 'PAST_WEEK', 'PAST_MONTH'
+ content_type (Optional[str]): Only return posts with this type of content.
+ Allowed values: 'PHOTOS', 'VIDEOS', 'JOBS', 'LIVE_VIDEOS', 'DOCUMENTS'
+ author_title (Optional[str]): Only return posts by authors whose title matches this value.
+ page (Optional[int]): Page number, starting at 1.
+
+ Returns:
+ SocialLinkedInPostSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_LINKEDIN_POSTS
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if date_posted is not None:
+ params["datePosted"] = date_posted
+ if content_type is not None:
+ params["contentType"] = content_type
+ if author_title is not None:
+ params["authorTitle"] = author_title
+ if page is not None:
+ params["page"] = page
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialLinkedInPostSearchResult.model_validate(resp.json())
+
+ # ----------------- search_people (sync) ----------------- #
+ def search_people(
+ self,
+ name: Optional[str] = None,
+ wikidata_id: Optional[List[str]] = None,
+ occupation_id: Optional[List[str]] = None,
+ occupation_label: Optional[str] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ ) -> PeopleSearchResult:
+ """
+ Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
+
+ Args:
+ name (Optional[str]): String. Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ wikidata_id (Optional[List[str]]): String Array. Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
+ occupation_id (Optional[List[str]]): String Array. Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
+ occupation_label (Optional[str]): String. Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of people to return per page in the paginated response.
+
+ Returns:
+ PeopleSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_PEOPLE
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if name is not None:
+ params["name"] = name
+ if wikidata_id is not None:
+ params["wikidataId"] = wikidata_id
+ if occupation_id is not None:
+ params["occupationId"] = occupation_id
+ if occupation_label is not None:
+ params["occupationLabel"] = occupation_label
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return PeopleSearchResult.model_validate(resp.json())
+
+ # ----------------- search_reddit_posts (sync) ----------------- #
+ def search_reddit_posts(
+ self,
+ q: str,
+ subreddit: Optional[str] = None,
+ sort: Optional[str] = None,
+ time: Optional[str] = None,
+ cursor: Optional[str] = None,
+ ) -> SocialRedditPostSearchResult:
+ """
+ Search Reddit posts, optionally within a single subreddit. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ q (str): Search query. (required)
+ subreddit (Optional[str]): Restrict the search to a subreddit (without the `r/` prefix).
+ sort (Optional[str]): Sort order of the results.
+ Allowed values: 'RELEVANCE', 'HOT', 'TOP', 'NEW', 'COMMENTS'
+ time (Optional[str]): Time window of the results. Only applies to the `TOP` sort.
+ Allowed values: 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR', 'ALL'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialRedditPostSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_REDDIT_POSTS
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if subreddit is not None:
+ params["subreddit"] = subreddit
+ if sort is not None:
+ params["sort"] = sort
+ if time is not None:
+ params["time"] = time
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialRedditPostSearchResult.model_validate(resp.json())
+
+ # ----------------- search_sources (sync) ----------------- #
+ def search_sources(
+ self,
+ domain: Optional[List[str]] = None,
+ name: Optional[str] = None,
+ source_group: Optional[str] = None,
+ sort_by: Optional[SourcesSortBy] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ min_monthly_visits: Optional[int] = None,
+ max_monthly_visits: Optional[int] = None,
+ min_monthly_posts: Optional[int] = None,
+ max_monthly_posts: Optional[int] = None,
+ country: Optional[List[str]] = None,
+ source_country: Optional[List[str]] = None,
+ source_state: Optional[List[str]] = None,
+ source_county: Optional[List[str]] = None,
+ source_city: Optional[List[str]] = None,
+ source_lat: Optional[float] = None,
+ source_lon: Optional[float] = None,
+ source_max_distance: Optional[float] = None,
+ category: Optional[List[str]] = None,
+ topic: Optional[List[str]] = None,
+ label: Optional[List[str]] = None,
+ paywall: Optional[bool] = None,
+ show_subdomains: Optional[bool] = None,
+ show_num_results: Optional[bool] = None,
+ ) -> SourceSearchResult:
+ """
+ Search and filter the 200,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
+
+ Args:
+ domain (Optional[List[str]]): String Array. Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
+ name (Optional[str]): String. Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ source_group (Optional[str]): String. Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
+ sort_by (Optional[SourcesSortBy]): String. Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of sources to return per page in the paginated response.
+ min_monthly_visits (Optional[int]): Integer. Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
+ max_monthly_visits (Optional[int]): Integer. Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
+ min_monthly_posts (Optional[int]): Integer. Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
+ max_monthly_posts (Optional[int]): Integer. Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
+ country (Optional[List[str]]): String Array. Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
+ source_country (Optional[List[str]]): String Array. Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
+ source_state (Optional[List[str]]): String Array. Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
+ source_county (Optional[List[str]]): String Array. Filter for local publications based in specific counties. Multiple values create an OR filter.
+ source_city (Optional[List[str]]): String Array. Filter for local publications based in specific cities. Multiple values create an OR filter.
+ source_lat (Optional[float]): Float. Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
+ source_lon (Optional[float]): Float. Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
+ source_max_distance (Optional[float]): Float. Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
+ category (Optional[List[str]]): String Array. Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
+ topic (Optional[List[str]]): String Array. Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
+ label (Optional[List[str]]): String Array. Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
+ paywall (Optional[bool]): Boolean. Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
+ show_subdomains (Optional[bool]): Boolean. Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
+ show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+
+ Returns:
+ SourceSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_SOURCES
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if domain is not None:
+ params["domain"] = domain
+ if name is not None:
+ params["name"] = name
+ if source_group is not None:
+ params["sourceGroup"] = source_group
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if min_monthly_visits is not None:
+ params["minMonthlyVisits"] = min_monthly_visits
+ if max_monthly_visits is not None:
+ params["maxMonthlyVisits"] = max_monthly_visits
+ if min_monthly_posts is not None:
+ params["minMonthlyPosts"] = min_monthly_posts
+ if max_monthly_posts is not None:
+ params["maxMonthlyPosts"] = max_monthly_posts
+ if country is not None:
+ params["country"] = country
+ if source_country is not None:
+ params["sourceCountry"] = source_country
+ if source_state is not None:
+ params["sourceState"] = source_state
+ if source_county is not None:
+ params["sourceCounty"] = source_county
+ if source_city is not None:
+ params["sourceCity"] = source_city
+ if source_lat is not None:
+ params["sourceLat"] = source_lat
+ if source_lon is not None:
+ params["sourceLon"] = source_lon
+ if source_max_distance is not None:
+ params["sourceMaxDistance"] = source_max_distance
+ if category is not None:
+ params["category"] = category
+ if topic is not None:
+ params["topic"] = topic
+ if label is not None:
+ params["label"] = label
+ if paywall is not None:
+ params["paywall"] = paywall
+ if show_subdomains is not None:
+ params["showSubdomains"] = show_subdomains
+ if show_num_results is not None:
+ params["showNumResults"] = show_num_results
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SourceSearchResult.model_validate(resp.json())
+
+ # ----------------- search_stories (sync) ----------------- #
+ def search_stories(
+ self,
+ q: Optional[str] = None,
+ name: Optional[str] = None,
+ cluster_id: Optional[List[str]] = None,
+ exclude_cluster_id: Optional[List[str]] = None,
+ sort_by: Optional[ClustersListSortBy] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ initialized_from: Optional[datetime] = None,
+ initialized_to: Optional[datetime] = None,
+ updated_from: Optional[datetime] = None,
+ updated_to: Optional[datetime] = None,
+ topic: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ taxonomy: Optional[List[str]] = None,
+ source: Optional[List[str]] = None,
+ source_group: Optional[List[str]] = None,
+ min_unique_sources: Optional[int] = None,
+ min_source_diversity: Optional[float] = None,
+ person_wikidata_id: Optional[List[str]] = None,
+ person_name: Optional[str] = None,
+ company_id: Optional[List[str]] = None,
+ company_name: Optional[str] = None,
+ company_domain: Optional[List[str]] = None,
+ company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ state: Optional[List[str]] = None,
+ city: Optional[List[str]] = None,
+ area: Optional[List[str]] = None,
+ min_cluster_size: Optional[int] = None,
+ max_cluster_size: Optional[int] = None,
+ name_exists: Optional[bool] = None,
+ positive_sentiment_from: Optional[float] = None,
+ positive_sentiment_to: Optional[float] = None,
+ neutral_sentiment_from: Optional[float] = None,
+ neutral_sentiment_to: Optional[float] = None,
+ negative_sentiment_from: Optional[float] = None,
+ negative_sentiment_to: Optional[float] = None,
+ show_story_page_info: Optional[bool] = None,
+ show_num_results: Optional[bool] = None,
+ show_duplicates: Optional[bool] = None,
+ show_highlighting: Optional[bool] = None,
+ highlight_fragment_size: Optional[int] = None,
+ highlight_num_fragments: Optional[int] = None,
+ highlight_pre_tag: Optional[str] = None,
+ highlight_post_tag: Optional[str] = None,
+ highlight_q: Optional[str] = None,
+ expand_articles: Optional[bool] = None,
+ ) -> StorySearchResult:
+ """
+ Track evolving narratives and top news headlines with the '**Stories**' endpoint β it returns structured clusters of related articles with summaries, key points, sentiment scores, and metadata on top people, companies, topics, countries, and categories. Use it to monitor how news stories develop over time, who and what they impact, and where they're gaining traction globally. Each story represents a cluster of related articles grouped during processing. Articles are assigned to a single story, identified by the `clusterId` field. Once a story reaches five articles, a story title is automatically generated based on its content. Stories update as new coverage is processed and key details evolve. To fetch all articles within a specific story, query the `/articles/all` endpoint using the `clusterId` parameter.
+
+ Args:
+ q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
+ cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
+ exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
+ sort_by (Optional[ClustersListSortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
+ var_from (Optional[datetime]): Filter for stories created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for stories created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ initialized_from (Optional[datetime]): Filter for stories initialized on or after this date. A story is initialized when it becomes visible with generated story data.
+ initialized_to (Optional[datetime]): Filter for stories initialized on or before this date. A story is initialized when it becomes visible with generated story data.
+ updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
+ updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
+ topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
+ category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
+ taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
+ source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
+ source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
+ min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
+ min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
+ person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
+ person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
+ company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
+ company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
+ company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
+ company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes of top mentioned companies (International Securities Identification Numbers). Returns stories where companies with these ISINs appear prominently. For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes on ticker listings of top mentioned companies (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. Returns stories where those companies appear prominently. For available company entities and their listing ISINs, consult the /companies endpoint.
+ country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
+ state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
+ max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
+ name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
+ positive_sentiment_from (Optional[float]): Filter stories with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ positive_sentiment_to (Optional[float]): Filter stories with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ neutral_sentiment_from (Optional[float]): Filter stories with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ neutral_sentiment_to (Optional[float]): Filter stories with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ negative_sentiment_from (Optional[float]): Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ negative_sentiment_to (Optional[float]): Filter stories with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ show_story_page_info (Optional[bool]): Boolean.
+ show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
+ show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
+ show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
+ highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
+ highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
+ highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
+ highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
+ highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
+ expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
+
+ Returns:
+ StorySearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_STORIES
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if name is not None:
+ params["name"] = name
+ if cluster_id is not None:
+ params["clusterId"] = cluster_id
+ if exclude_cluster_id is not None:
+ params["excludeClusterId"] = exclude_cluster_id
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if initialized_from is not None:
+ params["initializedFrom"] = initialized_from
+ if initialized_to is not None:
+ params["initializedTo"] = initialized_to
+ if updated_from is not None:
+ params["updatedFrom"] = updated_from
+ if updated_to is not None:
+ params["updatedTo"] = updated_to
+ if topic is not None:
+ params["topic"] = topic
+ if category is not None:
+ params["category"] = category
+ if taxonomy is not None:
+ params["taxonomy"] = taxonomy
+ if source is not None:
+ params["source"] = source
+ if source_group is not None:
+ params["sourceGroup"] = source_group
+ if min_unique_sources is not None:
+ params["minUniqueSources"] = min_unique_sources
+ if min_source_diversity is not None:
+ params["minSourceDiversity"] = min_source_diversity
+ if person_wikidata_id is not None:
+ params["personWikidataId"] = person_wikidata_id
+ if person_name is not None:
+ params["personName"] = person_name
+ if company_id is not None:
+ params["companyId"] = company_id
+ if company_name is not None:
+ params["companyName"] = company_name
+ if company_domain is not None:
+ params["companyDomain"] = company_domain
+ if company_symbol is not None:
+ params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if country is not None:
+ params["country"] = country
+ if state is not None:
+ params["state"] = state
+ if city is not None:
+ params["city"] = city
+ if area is not None:
+ params["area"] = area
+ if min_cluster_size is not None:
+ params["minClusterSize"] = min_cluster_size
+ if max_cluster_size is not None:
+ params["maxClusterSize"] = max_cluster_size
+ if name_exists is not None:
+ params["nameExists"] = name_exists
+ if positive_sentiment_from is not None:
+ params["positiveSentimentFrom"] = positive_sentiment_from
+ if positive_sentiment_to is not None:
+ params["positiveSentimentTo"] = positive_sentiment_to
+ if neutral_sentiment_from is not None:
+ params["neutralSentimentFrom"] = neutral_sentiment_from
+ if neutral_sentiment_to is not None:
+ params["neutralSentimentTo"] = neutral_sentiment_to
+ if negative_sentiment_from is not None:
+ params["negativeSentimentFrom"] = negative_sentiment_from
+ if negative_sentiment_to is not None:
+ params["negativeSentimentTo"] = negative_sentiment_to
+ if show_story_page_info is not None:
+ params["showStoryPageInfo"] = show_story_page_info
+ if show_num_results is not None:
+ params["showNumResults"] = show_num_results
+ if show_duplicates is not None:
+ params["showDuplicates"] = show_duplicates
+ if show_highlighting is not None:
+ params["showHighlighting"] = show_highlighting
+ if highlight_fragment_size is not None:
+ params["highlightFragmentSize"] = highlight_fragment_size
+ if highlight_num_fragments is not None:
+ params["highlightNumFragments"] = highlight_num_fragments
+ if highlight_pre_tag is not None:
+ params["highlightPreTag"] = highlight_pre_tag
+ if highlight_post_tag is not None:
+ params["highlightPostTag"] = highlight_post_tag
+ if highlight_q is not None:
+ params["highlightQ"] = highlight_q
+ if expand_articles is not None:
+ params["expandArticles"] = expand_articles
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return StorySearchResult.model_validate(resp.json())
+
+ # ----------------- search_summarizer (sync) ----------------- #
+ def search_summarizer(
+ self,
+ summary_body: SummaryBody,
+ q: Optional[str] = None,
+ title: Optional[str] = None,
+ desc: Optional[str] = None,
+ content: Optional[str] = None,
+ summary: Optional[str] = None,
+ url: Optional[str] = None,
+ article_id: Optional[List[str]] = None,
+ cluster_id: Optional[List[str]] = None,
+ sort_by: Optional[AllEndpointSortBy] = None,
+ page: Optional[int] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ add_date_from: Optional[datetime] = None,
+ add_date_to: Optional[datetime] = None,
+ refresh_date_from: Optional[datetime] = None,
+ refresh_date_to: Optional[datetime] = None,
+ medium: Optional[List[str]] = None,
+ source: Optional[List[str]] = None,
+ source_group: Optional[List[str]] = None,
+ exclude_source_group: Optional[List[str]] = None,
+ exclude_source: Optional[List[str]] = None,
+ watchlist: Optional[List[str]] = None,
+ exclude_watchlist: Optional[List[str]] = None,
+ paywall: Optional[bool] = None,
+ byline: Optional[List[str]] = None,
+ author: Optional[List[str]] = None,
+ exclude_author: Optional[List[str]] = None,
+ journalist_id: Optional[List[str]] = None,
+ exclude_journalist_id: Optional[List[str]] = None,
+ language: Optional[List[str]] = None,
+ exclude_language: Optional[List[str]] = None,
+ search_translation: Optional[bool] = None,
+ label: Optional[List[str]] = None,
+ exclude_label: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ exclude_category: Optional[List[str]] = None,
+ topic: Optional[List[str]] = None,
+ exclude_topic: Optional[List[str]] = None,
+ link_to: Optional[str] = None,
+ show_reprints: Optional[bool] = None,
+ reprint_group_id: Optional[str] = None,
+ city: Optional[List[str]] = None,
+ exclude_city: Optional[List[str]] = None,
+ area: Optional[List[str]] = None,
+ state: Optional[List[str]] = None,
+ exclude_state: Optional[List[str]] = None,
+ county: Optional[List[str]] = None,
+ exclude_county: Optional[List[str]] = None,
+ locations_country: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ exclude_locations_country: Optional[List[str]] = None,
+ location: Optional[List[str]] = None,
+ lat: Optional[float] = None,
+ lon: Optional[float] = None,
+ max_distance: Optional[float] = None,
+ source_city: Optional[List[str]] = None,
+ exclude_source_city: Optional[List[str]] = None,
+ source_county: Optional[List[str]] = None,
+ exclude_source_county: Optional[List[str]] = None,
+ source_country: Optional[List[str]] = None,
+ exclude_source_country: Optional[List[str]] = None,
+ source_state: Optional[List[str]] = None,
+ exclude_source_state: Optional[List[str]] = None,
+ source_lat: Optional[float] = None,
+ source_lon: Optional[float] = None,
+ source_max_distance: Optional[float] = None,
+ person_wikidata_id: Optional[List[str]] = None,
+ exclude_person_wikidata_id: Optional[List[str]] = None,
+ person_name: Optional[List[str]] = None,
+ exclude_person_name: Optional[List[str]] = None,
+ company_id: Optional[List[str]] = None,
+ exclude_company_id: Optional[List[str]] = None,
+ company_name: Optional[str] = None,
+ company_domain: Optional[List[str]] = None,
+ exclude_company_domain: Optional[List[str]] = None,
+ company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_isin: Optional[List[str]] = None,
+ exclude_company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_symbol: Optional[List[str]] = None,
+ positive_sentiment_from: Optional[float] = None,
+ positive_sentiment_to: Optional[float] = None,
+ neutral_sentiment_from: Optional[float] = None,
+ neutral_sentiment_to: Optional[float] = None,
+ negative_sentiment_from: Optional[float] = None,
+ negative_sentiment_to: Optional[float] = None,
+ taxonomy: Optional[List[str]] = None,
+ prefix_taxonomy: Optional[str] = None,
+ show_highlighting: Optional[bool] = None,
+ highlight_fragment_size: Optional[int] = None,
+ highlight_num_fragments: Optional[int] = None,
+ highlight_pre_tag: Optional[str] = None,
+ highlight_post_tag: Optional[str] = None,
+ highlight_q: Optional[str] = None,
+ ) -> SummarySearchResult:
+ """
+ Produce one concise summary from up to 100 articles matching the supplied filters. Use the prompt and summarization fields to control which insights are highlighted. When method is CLUSTERS, article-only parameters such as articleId, page, sortBy, watchlist, reprintGroupId, exclusion filters, prefixTaxonomy, and highlighting options are ignored.
+
+ Args:
+ summary_body (SummaryBody): Parameter summary_body (required)
+ q (Optional[str]): String. Primary search query for filtering articles based on their title, description, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ title (Optional[str]): String. Search specifically within article headlines/titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ desc (Optional[str]): String. Search within article description fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
+ content (Optional[str]): String. Search within the full article body content. Supports Boolean logic, exact phrase matching with quotes, and wildcards for comprehensive content searching.
+ summary (Optional[str]): String. Search within article summary fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
+ url (Optional[str]): String. Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel.
+ article_id (Optional[List[str]]): String Array. Retrieve specific news articles by their unique article identifiers. Multiple IDs can be provided to return a collection of specific articles.
+ cluster_id (Optional[List[str]]): String Array. Filter results to only show content within a specific related content cluster. Returns articles grouped together as part of Perigon Stories based on topic relevance.
+ sort_by (Optional[AllEndpointSortBy]): String. Determines the article sorting order. Options include relevance (default), date/pubDate (newest publication date first), reverseDate (oldest publication date first), addDate (newest ingestion date first), reverseAddDate (oldest ingestion date first), and refreshDate (most recently updated in system first, often identical to addDate).
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ var_from (Optional[datetime]): Date. Filter for articles published after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ to (Optional[datetime]): Date. Filter for articles published before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ add_date_from (Optional[datetime]): Date. Filter for articles added to Perigon's system after this date. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
+ add_date_to (Optional[datetime]): Date. Filter for articles added to Perigon's system before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ refresh_date_from (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system after this date. In most cases yields similar results to addDateFrom but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
+ refresh_date_to (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system before this date. In most cases yields similar results to addDateTo but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ medium (Optional[List[str]]): String Array. Filter articles by their primary medium type. Accepts Article for written content or Video for video-based stories. Multiple values create an OR filter.
+ source (Optional[List[str]]): String Array. Filter articles by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
+ source_group (Optional[List[str]]): String Array. Filter articles using Perigon's curated publisher bundles (e.g., top100, top25crypto). Multiple values create an OR filter to include articles from any of the specified bundles.
+ exclude_source_group (Optional[List[str]]): String Array. Exclude articles from specified Perigon source groups. Multiple values create an AND-exclude filter, removing content from publishers in any of the specified bundles (e.g., top10, top100).
+ exclude_source (Optional[List[str]]): String Array. Exclude articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an AND-exclude filter.
+ watchlist (Optional[List[str]]): String Array. Filter articles using watchlists of people and companies. Multiple values create an OR filter to include articles mentioning any entity from the specified watchlists.
exclude_watchlist (Optional[List[str]]): String Array. Exclude articles mentioning entities from specified watchlists. Multiple values create an AND-exclude filter, removing content mentioning any entity from the specified watchlists.
paywall (Optional[bool]): Boolean. Filter to show only results where the source has a paywall (true) or does not have a paywall (false).
byline (Optional[List[str]]): String Array. Filter articles by author bylines. Works as an exact match for each author name provided. Multiple values create an OR filter to find articles by any of the specified authors.
@@ -1814,7 +2630,7 @@ async def search_articles_async(
source_state (Optional[List[str]]): String Array. Find articles published by sources that are located within a given state.
exclude_source_state (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified states.
source_lat (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
- source_lon (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
+ source_lon (Optional[float]): Float. Longitude of the center point to search articles created by local publications.
source_max_distance (Optional[float]): Float. Maximum distance from starting point to search articles created by local publications.
person_wikidata_id (Optional[List[str]]): String Array. Filter articles by Wikidata IDs of mentioned people. Refer to the /people endpoint for a complete list of tracked individuals.
exclude_person_wikidata_id (Optional[List[str]]): String Array. Exclude articles mentioning people with specific Wikidata IDs. Creates an AND-exclude filter to remove content about these individuals. Uses precise identifiers to avoid name ambiguity.
@@ -1826,8 +2642,11 @@ async def search_articles_async(
company_domain (Optional[List[str]]): String Array. Filter articles by company domains (e.g., apple.com). For available company entities, consult the /companies endpoint.
exclude_company_domain (Optional[List[str]]): String Array. Exclude articles related to companies with specific domains. Creates an AND-exclude filter to remove content about these companies.
company_symbol (Optional[List[str]]): String Array. Filter articles by company stock symbols. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter articles by company ISIN codes (International Securities Identification Numbers). For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter articles by ISIN codes on company ticker listings (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
+ exclude_company_isin (Optional[List[str]]): String Array. Exclude articles related to companies with specific ISIN codes. Creates an AND-exclude filter to remove content about these companies. For available company entities and their ISINs, consult the /companies endpoint.
+ exclude_company_symbol_isin (Optional[List[str]]): String Array. Exclude articles related to companies whose ticker listings have these ISIN codes (symbols.isin). Distinct from excludeCompanyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
exclude_company_symbol (Optional[List[str]]): String Array. A list of stock symbols (ticker symbols) that identify companies to be excluded. Articles related to companies using any of these symbols will be omitted, which is useful for targeting or avoiding specific public companies.
- show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.
positive_sentiment_from (Optional[float]): Float. Filter articles with a positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
positive_sentiment_to (Optional[float]): Float. Filter articles with a positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
neutral_sentiment_from (Optional[float]): Float. Filter articles with a neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
@@ -1844,170 +2663,1219 @@ async def search_articles_async(
highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
Returns:
- QuerySearchResult: The response
+ SummarySearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_SUMMARIZER
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if title is not None:
+ params["title"] = title
+ if desc is not None:
+ params["desc"] = desc
+ if content is not None:
+ params["content"] = content
+ if summary is not None:
+ params["summary"] = summary
+ if url is not None:
+ params["url"] = url
+ if article_id is not None:
+ params["articleId"] = article_id
+ if cluster_id is not None:
+ params["clusterId"] = cluster_id
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if add_date_from is not None:
+ params["addDateFrom"] = add_date_from
+ if add_date_to is not None:
+ params["addDateTo"] = add_date_to
+ if refresh_date_from is not None:
+ params["refreshDateFrom"] = refresh_date_from
+ if refresh_date_to is not None:
+ params["refreshDateTo"] = refresh_date_to
+ if medium is not None:
+ params["medium"] = medium
+ if source is not None:
+ params["source"] = source
+ if source_group is not None:
+ params["sourceGroup"] = source_group
+ if exclude_source_group is not None:
+ params["excludeSourceGroup"] = exclude_source_group
+ if exclude_source is not None:
+ params["excludeSource"] = exclude_source
+ if watchlist is not None:
+ params["watchlist"] = watchlist
+ if exclude_watchlist is not None:
+ params["excludeWatchlist"] = exclude_watchlist
+ if paywall is not None:
+ params["paywall"] = paywall
+ if byline is not None:
+ params["byline"] = byline
+ if author is not None:
+ params["author"] = author
+ if exclude_author is not None:
+ params["excludeAuthor"] = exclude_author
+ if journalist_id is not None:
+ params["journalistId"] = journalist_id
+ if exclude_journalist_id is not None:
+ params["excludeJournalistId"] = exclude_journalist_id
+ if language is not None:
+ params["language"] = language
+ if exclude_language is not None:
+ params["excludeLanguage"] = exclude_language
+ if search_translation is not None:
+ params["searchTranslation"] = search_translation
+ if label is not None:
+ params["label"] = label
+ if exclude_label is not None:
+ params["excludeLabel"] = exclude_label
+ if category is not None:
+ params["category"] = category
+ if exclude_category is not None:
+ params["excludeCategory"] = exclude_category
+ if topic is not None:
+ params["topic"] = topic
+ if exclude_topic is not None:
+ params["excludeTopic"] = exclude_topic
+ if link_to is not None:
+ params["linkTo"] = link_to
+ if show_reprints is not None:
+ params["showReprints"] = show_reprints
+ if reprint_group_id is not None:
+ params["reprintGroupId"] = reprint_group_id
+ if city is not None:
+ params["city"] = city
+ if exclude_city is not None:
+ params["excludeCity"] = exclude_city
+ if area is not None:
+ params["area"] = area
+ if state is not None:
+ params["state"] = state
+ if exclude_state is not None:
+ params["excludeState"] = exclude_state
+ if county is not None:
+ params["county"] = county
+ if exclude_county is not None:
+ params["excludeCounty"] = exclude_county
+ if locations_country is not None:
+ params["locationsCountry"] = locations_country
+ if country is not None:
+ params["country"] = country
+ if exclude_locations_country is not None:
+ params["excludeLocationsCountry"] = exclude_locations_country
+ if location is not None:
+ params["location"] = location
+ if lat is not None:
+ params["lat"] = lat
+ if lon is not None:
+ params["lon"] = lon
+ if max_distance is not None:
+ params["maxDistance"] = max_distance
+ if source_city is not None:
+ params["sourceCity"] = source_city
+ if exclude_source_city is not None:
+ params["excludeSourceCity"] = exclude_source_city
+ if source_county is not None:
+ params["sourceCounty"] = source_county
+ if exclude_source_county is not None:
+ params["excludeSourceCounty"] = exclude_source_county
+ if source_country is not None:
+ params["sourceCountry"] = source_country
+ if exclude_source_country is not None:
+ params["excludeSourceCountry"] = exclude_source_country
+ if source_state is not None:
+ params["sourceState"] = source_state
+ if exclude_source_state is not None:
+ params["excludeSourceState"] = exclude_source_state
+ if source_lat is not None:
+ params["sourceLat"] = source_lat
+ if source_lon is not None:
+ params["sourceLon"] = source_lon
+ if source_max_distance is not None:
+ params["sourceMaxDistance"] = source_max_distance
+ if person_wikidata_id is not None:
+ params["personWikidataId"] = person_wikidata_id
+ if exclude_person_wikidata_id is not None:
+ params["excludePersonWikidataId"] = exclude_person_wikidata_id
+ if person_name is not None:
+ params["personName"] = person_name
+ if exclude_person_name is not None:
+ params["excludePersonName"] = exclude_person_name
+ if company_id is not None:
+ params["companyId"] = company_id
+ if exclude_company_id is not None:
+ params["excludeCompanyId"] = exclude_company_id
+ if company_name is not None:
+ params["companyName"] = company_name
+ if company_domain is not None:
+ params["companyDomain"] = company_domain
+ if exclude_company_domain is not None:
+ params["excludeCompanyDomain"] = exclude_company_domain
+ if company_symbol is not None:
+ params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if exclude_company_isin is not None:
+ params["excludeCompanyIsin"] = exclude_company_isin
+ if exclude_company_symbol_isin is not None:
+ params["excludeCompanySymbolIsin"] = exclude_company_symbol_isin
+ if exclude_company_symbol is not None:
+ params["excludeCompanySymbol"] = exclude_company_symbol
+ if positive_sentiment_from is not None:
+ params["positiveSentimentFrom"] = positive_sentiment_from
+ if positive_sentiment_to is not None:
+ params["positiveSentimentTo"] = positive_sentiment_to
+ if neutral_sentiment_from is not None:
+ params["neutralSentimentFrom"] = neutral_sentiment_from
+ if neutral_sentiment_to is not None:
+ params["neutralSentimentTo"] = neutral_sentiment_to
+ if negative_sentiment_from is not None:
+ params["negativeSentimentFrom"] = negative_sentiment_from
+ if negative_sentiment_to is not None:
+ params["negativeSentimentTo"] = negative_sentiment_to
+ if taxonomy is not None:
+ params["taxonomy"] = taxonomy
+ if prefix_taxonomy is not None:
+ params["prefixTaxonomy"] = prefix_taxonomy
+ if show_highlighting is not None:
+ params["showHighlighting"] = show_highlighting
+ if highlight_fragment_size is not None:
+ params["highlightFragmentSize"] = highlight_fragment_size
+ if highlight_num_fragments is not None:
+ params["highlightNumFragments"] = highlight_num_fragments
+ if highlight_pre_tag is not None:
+ params["highlightPreTag"] = highlight_pre_tag
+ if highlight_post_tag is not None:
+ params["highlightPostTag"] = highlight_post_tag
+ if highlight_q is not None:
+ params["highlightQ"] = highlight_q
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(summary_body),
+ )
+ resp.raise_for_status()
+ return SummarySearchResult.model_validate(resp.json())
+
+ # ----------------- search_topics (sync) ----------------- #
+ def search_topics(
+ self,
+ name: Optional[str] = None,
+ category: Optional[str] = None,
+ subcategory: Optional[str] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ ) -> TopicSearchResult:
+ """
+ Search through all available Topics that exist within the Perigon Database.
+
+ Args:
+ name (Optional[str]): String. Search for topics by exact name or partial text match. Does not support wildcards. Examples include Markets, Cryptocurrency, Climate Change, etc.
+ category (Optional[str]): String. Filter topics by broad article categories such as Politics, Tech, Sports, Business, Finance, Entertainment, etc.
+ subcategory (Optional[str]): String. Filter topics by their specific subcategory. Subcategories provide more granular classification beyond the main category, such as TV or Event.
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of topics to return per page in the paginated response.
+
+ Returns:
+ TopicSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_TOPICS
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if name is not None:
+ params["name"] = name
+ if category is not None:
+ params["category"] = category
+ if subcategory is not None:
+ params["subcategory"] = subcategory
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return TopicSearchResult.model_validate(resp.json())
+
+ # ----------------- search_wikipedia (sync) ----------------- #
+ def search_wikipedia(
+ self,
+ q: Optional[str] = None,
+ title: Optional[str] = None,
+ summary: Optional[str] = None,
+ text: Optional[str] = None,
+ reference: Optional[str] = None,
+ id: Optional[List[str]] = None,
+ wiki_page_id: Optional[List[int]] = None,
+ wiki_revision_id: Optional[List[int]] = None,
+ wiki_code: Optional[List[str]] = None,
+ wiki_namespace: Optional[List[int]] = None,
+ wikidata_id: Optional[List[str]] = None,
+ wikidata_instance_of_id: Optional[List[str]] = None,
+ wikidata_instance_of_label: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ section_id: Optional[List[str]] = None,
+ wiki_revision_from: Optional[datetime] = None,
+ wiki_revision_to: Optional[datetime] = None,
+ scraped_at_from: Optional[datetime] = None,
+ scraped_at_to: Optional[datetime] = None,
+ pageviews_from: Optional[int] = None,
+ pageviews_to: Optional[int] = None,
+ with_pageviews: Optional[bool] = None,
+ show_num_results: Optional[bool] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ sort_by: Optional[str] = None,
+ ) -> WikipediaSearchResult:
+ """
+ Search and filter all Wikipedia pages available via the Perigon API. The result includes a list of individual pages that were matched to your specific criteria.
+
+ Args:
+ q (Optional[str]): String. Primary search query for filtering pages based on their title, summary, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ title (Optional[str]): String. Search specifically within page titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ summary (Optional[str]): String. Search specifically within page summary. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ text (Optional[str]): String. Search specifically within the page's content (across all sections). Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ reference (Optional[str]): String. Search specifically across page's references. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ id (Optional[List[str]]): String Array. Retrieve specific pages by their unique Perigon identifiers. Multiple IDs can be provided to return a collection of specific pages.
+ wiki_page_id (Optional[List[int]]): Integer Array. Retrieve specific pages by their Wikipedia identifiers. These are unique only in a combination with `wikiCode` parameter. Multiple IDs can be provided to return a collection of specific pages.
+ wiki_revision_id (Optional[List[int]]): Integer Array. Retrieve specific pages by their Wikipedia revision identifiers. These are unique only in a combination with `wikiCode` parameter. Multiple IDs can be provided to return a collection of specific pages. This ID changes each time a page is edited.
+ wiki_code (Optional[List[str]]): String Array. Retrieve pages only from specified wiki projects. Currently, the only accepted value is `enwiki`.
+ wiki_namespace (Optional[List[int]]): Integer Array. Retrieve pages only from specified wiki namespace. Currently, only the main namespace (0) is available.
+ wikidata_id (Optional[List[str]]): String Array. Retrieve pages by the ids corresponding to their Wikidata entities.
+ wikidata_instance_of_id (Optional[List[str]]): String Array. Retrieve all pages whose Wikidata entities are instances of these provided ids.
+ wikidata_instance_of_label (Optional[List[str]]): String Array. Retrieve all pages whose Wikidata entities are instances of these ids (provided as labels).
+ category (Optional[List[str]]): String Array. Retrieve all pages for specified categories.
+ section_id (Optional[List[str]]): String Array. Retrieve pages containing provided section ids. Each section ID is unique.
+ wiki_revision_from (Optional[datetime]): Date. Retrieve pages modified after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ wiki_revision_to (Optional[datetime]): Date. Retrieve pages modified before this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ scraped_at_from (Optional[datetime]): Date. Retrieve pages scraped after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ scraped_at_to (Optional[datetime]): Date. Retrieve pages scraped before this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ pageviews_from (Optional[int]): Integer. Retrieve pages with the average number of views per day higher than the provided value.
+ pageviews_to (Optional[int]): Integer. Retrieve pages with the average number of views per day lower than the provided value.
+ with_pageviews (Optional[bool]): Boolean. Retrieve pages that have any viewership statistics available for them. If `false` (the default) - return all pages.
+ show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched pages. Default value is false which makes queries a bit more efficient but also counts up to 10000 pages.
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
+ sort_by (Optional[str]): String. Determines the Wikipedia page sorting order. Options include relevance (default), revisionTsDesc (recently edited first), revisionTsAsc (recently edited last), pageViewsDesc (highest viewership first), pageViewsAsc (highest viewership last), scrapedAtDesc (recently scraped first), scrapedAtAsc (recently scraped last).
+ Allowed values: 'relevance', 'revisionTsDesc', 'revisionTsAsc', 'pageViewsDesc', 'pageViewsAsc', 'scrapedAtDesc', 'scrapedAtAsc'
+
+ Returns:
+ WikipediaSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_WIKIPEDIA
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if title is not None:
+ params["title"] = title
+ if summary is not None:
+ params["summary"] = summary
+ if text is not None:
+ params["text"] = text
+ if reference is not None:
+ params["reference"] = reference
+ if id is not None:
+ params["id"] = id
+ if wiki_page_id is not None:
+ params["wikiPageId"] = wiki_page_id
+ if wiki_revision_id is not None:
+ params["wikiRevisionId"] = wiki_revision_id
+ if wiki_code is not None:
+ params["wikiCode"] = wiki_code
+ if wiki_namespace is not None:
+ params["wikiNamespace"] = wiki_namespace
+ if wikidata_id is not None:
+ params["wikidataId"] = wikidata_id
+ if wikidata_instance_of_id is not None:
+ params["wikidataInstanceOfId"] = wikidata_instance_of_id
+ if wikidata_instance_of_label is not None:
+ params["wikidataInstanceOfLabel"] = wikidata_instance_of_label
+ if category is not None:
+ params["category"] = category
+ if section_id is not None:
+ params["sectionId"] = section_id
+ if wiki_revision_from is not None:
+ params["wikiRevisionFrom"] = wiki_revision_from
+ if wiki_revision_to is not None:
+ params["wikiRevisionTo"] = wiki_revision_to
+ if scraped_at_from is not None:
+ params["scrapedAtFrom"] = scraped_at_from
+ if scraped_at_to is not None:
+ params["scrapedAtTo"] = scraped_at_to
+ if pageviews_from is not None:
+ params["pageviewsFrom"] = pageviews_from
+ if pageviews_to is not None:
+ params["pageviewsTo"] = pageviews_to
+ if with_pageviews is not None:
+ params["withPageviews"] = with_pageviews
+ if show_num_results is not None:
+ params["showNumResults"] = show_num_results
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return WikipediaSearchResult.model_validate(resp.json())
+
+ # ----------------- search_x_posts (sync) ----------------- #
+ def search_x_posts(
+ self, q: str, search_type: str, cursor: Optional[str] = None
+ ) -> SocialXPostSearchResult:
+ """
+ Search recent posts on X (Twitter). Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ q (str): Search query. Supports X search operators, e.g. `from:perigon`, `#ai` or `\"exact phrase\"`. (required)
+ search_type (str): Which posts to return: top, latest or media posts. (required)
+ Allowed values: 'TOP', 'LATEST', 'MEDIA'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialXPostSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_X_POSTS
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if search_type is not None:
+ params["searchType"] = search_type
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialXPostSearchResult.model_validate(resp.json())
+
+ # ----------------- update_contact_point (sync) ----------------- #
+ def update_contact_point(
+ self, uuid: UUID, contact_point_update_body: ContactPointUpdateBody
+ ) -> ContactPointResult:
+ """
+ Partially update a contact point owned by the organization associated with the API key. Only provided fields will be updated.
+
+ Args:
+ uuid (UUID): Parameter uuid (required)
+ contact_point_update_body (ContactPointUpdateBody): Parameter contact_point_update_body (required)
+
+ Returns:
+ ContactPointResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_UPDATE_CONTACT_POINT
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "PATCH",
+ path,
+ params=params,
+ json=_json_body(contact_point_update_body),
+ )
+ resp.raise_for_status()
+ return ContactPointResult.model_validate(resp.json())
+
+ # ----------------- update_monitor_api (sync) ----------------- #
+ def update_monitor_api(
+ self, uuid: UUID, signal_api_update_body: SignalApiUpdateBody
+ ) -> MonitorResult:
+ """
+ Partially update a monitor. Only provided fields are modified. Use /activate, /pause, or DELETE to change monitor status.
+
+ Args:
+ uuid (UUID): UUID of the monitor to update. (required)
+ signal_api_update_body (SignalApiUpdateBody): Parameter signal_api_update_body (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_UPDATE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "PATCH",
+ path,
+ params=params,
+ json=_json_body(signal_api_update_body),
+ )
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- update_source_group (sync) ----------------- #
+ def update_source_group(
+ self, id: int, patch_source_group_params: PatchSourceGroupParams
+ ) -> SourceGroupResult:
+ """
+ Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
+
+ Args:
+ id (int): Parameter id (required)
+ patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
+
+ Returns:
+ SourceGroupResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_UPDATE_SOURCE_GROUP
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "PATCH",
+ path,
+ params=params,
+ json=_json_body(patch_source_group_params),
+ )
+ resp.raise_for_status()
+ return SourceGroupResult.model_validate(resp.json())
+
+ # ----------------- update_watchlist (sync) ----------------- #
+ def update_watchlist(
+ self, id: int, update_watchlist_params: UpdateWatchlistParams
+ ) -> WatchlistResult:
+ """
+ Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
+
+ Args:
+ id (int): Parameter id (required)
+ update_watchlist_params (UpdateWatchlistParams): Parameter update_watchlist_params (required)
+
+ Returns:
+ WatchlistResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_UPDATE_WATCHLIST
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "PATCH",
+ path,
+ params=params,
+ json=_json_body(update_watchlist_params),
+ )
+ resp.raise_for_status()
+ return WatchlistResult.model_validate(resp.json())
+
+ # ----------------- vector_search_articles (sync) ----------------- #
+ def vector_search_articles(
+ self, article_search_params: ArticleSearchParams
+ ) -> ArticlesVectorSearchResult:
+ """
+ Perform a natural language search over news articles from the past 6 months using semantic relevance. The result includes a list of articles most closely matched to your query intent.
+
+ Args:
+ article_search_params (ArticleSearchParams): Parameter article_search_params (required)
+
+ Returns:
+ ArticlesVectorSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_VECTOR_SEARCH_ARTICLES
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(article_search_params),
+ )
+ resp.raise_for_status()
+ return ArticlesVectorSearchResult.model_validate(resp.json())
+
+ # ----------------- vector_search_wikipedia (sync) ----------------- #
+ def vector_search_wikipedia(
+ self, wikipedia_search_params: WikipediaSearchParams
+ ) -> WikipediaVectorSearchResult:
+ """
+ Perform a natural language search over Wikipedia pages using semantic relevance. The result includes a list of page sections most closely matched to your query intent.
+
+ Args:
+ wikipedia_search_params (WikipediaSearchParams): Parameter wikipedia_search_params (required)
+
+ Returns:
+ WikipediaVectorSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_VECTOR_SEARCH_WIKIPEDIA
+
+ # --- build query dict on the fly ---
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(wikipedia_search_params),
+ )
+ resp.raise_for_status()
+ return WikipediaVectorSearchResult.model_validate(resp.json())
+
+
+class AsyncV1Api:
+ """Async"""
+
+ def __init__(
+ self,
+ api_client: Optional[AsyncApiClient] = None,
+ *,
+ api_key: Optional[str] = None,
+ base_url: Optional[str] = None,
+ timeout: Optional[float] = None,
+ configuration: Optional[Configuration] = None,
+ ) -> None:
+ self.api_client, self._owns_client = bind_client(
+ api_client,
+ AsyncApiClient,
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ )
+
+ async def __aenter__(self) -> AsyncV1Api:
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
+ await self.close()
+
+ async def close(self) -> None:
+ """Close the HTTP client when this API created it."""
+ if self._owns_client:
+ await self.api_client.close()
+
+ # ----------------- activate_monitor_api (async) ----------------- #
+ async def activate_monitor_api(self, uuid: UUID) -> MonitorResult:
+ """
+ Activate a monitor, starting its processing pipeline.
+
+ Args:
+ uuid (UUID): UUID of the monitor to activate. (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_ACTIVATE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("POST", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- archive_monitor_api (async) ----------------- #
+ async def archive_monitor_api(self, uuid: UUID) -> MonitorResult:
+ """
+ Archive a monitor. Archived monitors are excluded from list results by default.
+
+ Args:
+ uuid (UUID): UUID of the monitor to archive. (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_ARCHIVE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("DELETE", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- create_contact_point (async) ----------------- #
+ async def create_contact_point(
+ self, contact_point_request_body: ContactPointRequestBody
+ ) -> ContactPointResult:
+ """
+ Create a new contact point under the organization associated with the API key. Only WEBHOOK contact points can be created via the API.
+
+ Args:
+ contact_point_request_body (ContactPointRequestBody): Parameter contact_point_request_body (required)
+
+ Returns:
+ ContactPointResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_CREATE_CONTACT_POINT
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(contact_point_request_body),
+ )
+ resp.raise_for_status()
+ return ContactPointResult.model_validate(resp.json())
+
+ # ----------------- create_monitor_api (async) ----------------- #
+ async def create_monitor_api(
+ self, signal_api_create_body: SignalApiCreateBody
+ ) -> MonitorResult:
+ """
+ Create a new AI monitor. Only AI_SIGNAL type is supported; status is required and accepts ACTIVE or DRAFT.
+
+ Args:
+ signal_api_create_body (SignalApiCreateBody): Parameter signal_api_create_body (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_CREATE_MONITOR_API
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(signal_api_create_body),
+ )
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- create_source_group (async) ----------------- #
+ async def create_source_group(
+ self, create_source_group_params: CreateSourceGroupParams
+ ) -> SourceGroupResult:
+ """
+ Create a new source group under the organization associated with the API key.
+
+ Args:
+ create_source_group_params (CreateSourceGroupParams): Parameter create_source_group_params (required)
+
+ Returns:
+ SourceGroupResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_CREATE_SOURCE_GROUP
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(create_source_group_params),
+ )
+ resp.raise_for_status()
+ return SourceGroupResult.model_validate(resp.json())
+
+ # ----------------- create_watchlist (async) ----------------- #
+ async def create_watchlist(
+ self, create_watchlist_params: CreateWatchlistParams
+ ) -> WatchlistResult:
+ """
+ Create a new watchlist under the organization associated with the API key. A watchlist can contain up to 100 combined people and companies.
+
+ Args:
+ create_watchlist_params (CreateWatchlistParams): Parameter create_watchlist_params (required)
+
+ Returns:
+ WatchlistResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_CREATE_WATCHLIST
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request(
+ "POST",
+ path,
+ params=params,
+ json=_json_body(create_watchlist_params),
+ )
+ resp.raise_for_status()
+ return WatchlistResult.model_validate(resp.json())
+
+ # ----------------- delete_contact_point (async) ----------------- #
+ async def delete_contact_point(self, uuid: UUID) -> ContactPointResult:
+ """
+ Delete a contact point owned by the organization associated with the API key. Contact points with historical notifications are archived rather than hard-deleted.
+
+ Args:
+ uuid (UUID): Parameter uuid (required)
+
+ Returns:
+ ContactPointResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_DELETE_CONTACT_POINT
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("DELETE", path, params=params)
+ resp.raise_for_status()
+ return ContactPointResult.model_validate(resp.json())
+
+ # ----------------- delete_source_group (async) ----------------- #
+ async def delete_source_group(self, id: int) -> SourceGroupResult:
+ """
+ Delete a source group owned by the organization associated with the API key.
+
+ Args:
+ id (int): Parameter id (required)
+
+ Returns:
+ SourceGroupResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_DELETE_SOURCE_GROUP
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("DELETE", path, params=params)
+ resp.raise_for_status()
+ return SourceGroupResult.model_validate(resp.json())
+
+ # ----------------- delete_watchlist (async) ----------------- #
+ async def delete_watchlist(self, id: int) -> WatchlistResult:
+ """
+ Delete a watchlist owned by the organization associated with the API key. A watchlist cannot be deleted if it is attached to active monitors.
+
+ Args:
+ id (int): Parameter id (required)
+
+ Returns:
+ WatchlistResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_DELETE_WATCHLIST
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("DELETE", path, params=params)
+ resp.raise_for_status()
+ return WatchlistResult.model_validate(resp.json())
+
+ # ----------------- get_contact_point (async) ----------------- #
+ async def get_contact_point(self, uuid: UUID) -> ContactPointResult:
+ """
+ Retrieve a contact point by UUID. Only returns contact points owned by the organization associated with the API key. FASTN contact points are not accessible via this endpoint.
+
+ Args:
+ uuid (UUID): Parameter uuid (required)
+
+ Returns:
+ ContactPointResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_CONTACT_POINT
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return ContactPointResult.model_validate(resp.json())
+
+ # ----------------- get_journalist_by_id (async) ----------------- #
+ async def get_journalist_by_id(self, id: str) -> Journalist:
+ """
+ Find additional details on a journalist by using the journalist ID found in an article response object.
+
+ Args:
+ id (str): Parameter id (required)
+
+ Returns:
+ Journalist: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_JOURNALIST_BY_ID
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return Journalist.model_validate(resp.json())
+
+ # ----------------- get_limits (async) ----------------- #
+ async def get_limits(self, api_keys: Optional[List[str]] = None) -> LimitsResult:
+ """
+ Returns the authenticated account's current API usage and the limits that apply to it: the number of requests made in the current tracking period, the request limit, the per-second rate limit, and when the counter resets. Optionally, pass one or more API keys via the `apiKeys` parameter to check their status. For each supplied key the response reports whether it is `valid` (exists and belongs to your account), `enabled` (a disabled key is 'off'), and whether its `subscription` is active. This endpoint is authenticated with your API key but is quota-exempt: calling it does **not** count against your monthly request quota and is not subject to rate limiting. It is intended for liveness/health probes and external dashboards that need to check whether an API key and subscription are active without consuming usage.
+
+ Args:
+ api_keys (Optional[List[str]]): Optional list of API keys whose validity and status should be checked. Each key is reported as valid only if it exists and belongs to your account.
+
+ Returns:
+ LimitsResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_LIMITS
+
+ params: Dict[str, Any] = {}
+ if api_keys is not None:
+ params["apiKeys"] = api_keys
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return LimitsResult.model_validate(resp.json())
+
+ # ----------------- get_linkedin_company_posts (async) ----------------- #
+ async def get_linkedin_company_posts(
+ self, url: str, type: Optional[str] = None, cursor: Optional[str] = None
+ ) -> SocialLinkedInFeedResult:
+ """
+ Get the recent posts of a LinkedIn company page, newest first. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ url (str): LinkedIn company page URL, e.g. `https://www.linkedin.com/company/microsoft`. (required)
+ type (Optional[str]): Kind of posts to return. Defaults to all posts.
+ Allowed values: 'ALL', 'ARTICLES', 'DOCUMENTS'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialLinkedInFeedResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_LINKEDIN_COMPANY_POSTS
+
+ params: Dict[str, Any] = {}
+ if url is not None:
+ params["url"] = url
+ if type is not None:
+ params["type"] = type
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialLinkedInFeedResult.model_validate(resp.json())
+
+ # ----------------- get_linkedin_person_activity (async) ----------------- #
+ async def get_linkedin_person_activity(
+ self, url: str, type: Optional[str] = None, cursor: Optional[str] = None
+ ) -> SocialLinkedInFeedResult:
+ """
+ Get the recent posts, comments or reactions of a LinkedIn member, newest first. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ url (str): LinkedIn profile URL, e.g. `https://www.linkedin.com/in/williamhgates`. (required)
+ type (Optional[str]): Kind of activity to return. Defaults to posts.
+ Allowed values: 'POSTS', 'COMMENTS', 'REACTIONS'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialLinkedInFeedResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_LINKEDIN_PERSON_ACTIVITY
+
+ params: Dict[str, Any] = {}
+ if url is not None:
+ params["url"] = url
+ if type is not None:
+ params["type"] = type
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialLinkedInFeedResult.model_validate(resp.json())
+
+ # ----------------- get_monitor_api (async) ----------------- #
+ async def get_monitor_api(self, uuid: UUID) -> MonitorResult:
+ """
+ Retrieve a monitor by UUID for the organization associated with the API key.
+
+ Args:
+ uuid (UUID): UUID of the monitor to retrieve. (required)
+
+ Returns:
+ MonitorResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- get_source_group (async) ----------------- #
+ async def get_source_group(self, id: int) -> SourceGroupResult:
+ """
+ Retrieve a source group by ID. Only returns source groups owned by the organization associated with the API key.
+
+ Args:
+ id (int): Parameter id (required)
+
+ Returns:
+ SourceGroupResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_SOURCE_GROUP
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SourceGroupResult.model_validate(resp.json())
+
+ # ----------------- get_story_counts (async) ----------------- #
+ async def get_story_counts(
+ self,
+ split_by: str,
+ q: Optional[str] = None,
+ name: Optional[str] = None,
+ cluster_id: Optional[List[str]] = None,
+ exclude_cluster_id: Optional[List[str]] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ initialized_from: Optional[datetime] = None,
+ initialized_to: Optional[datetime] = None,
+ updated_from: Optional[datetime] = None,
+ updated_to: Optional[datetime] = None,
+ topic: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ taxonomy: Optional[List[str]] = None,
+ source: Optional[List[str]] = None,
+ source_group: Optional[List[str]] = None,
+ min_unique_sources: Optional[int] = None,
+ min_source_diversity: Optional[float] = None,
+ person_wikidata_id: Optional[List[str]] = None,
+ person_name: Optional[str] = None,
+ company_id: Optional[List[str]] = None,
+ company_name: Optional[str] = None,
+ company_domain: Optional[List[str]] = None,
+ company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ state: Optional[List[str]] = None,
+ city: Optional[List[str]] = None,
+ area: Optional[List[str]] = None,
+ min_cluster_size: Optional[int] = None,
+ max_cluster_size: Optional[int] = None,
+ name_exists: Optional[bool] = None,
+ positive_sentiment_from: Optional[float] = None,
+ positive_sentiment_to: Optional[float] = None,
+ neutral_sentiment_from: Optional[float] = None,
+ neutral_sentiment_to: Optional[float] = None,
+ negative_sentiment_from: Optional[float] = None,
+ negative_sentiment_to: Optional[float] = None,
+ show_duplicates: Optional[bool] = None,
+ time_zone: Optional[str] = None,
+ ) -> StatResult:
+ """
+ Count matching stories by their creation time. Results can be grouped by hour, day, week, or month and filtered by supported story attributes.
+
+ Args:
+ split_by (str): Time interval used to group story counts. Accepts hour, day, week, or month and defaults to month. (required)
+ Allowed values: 'hour', 'day', 'week', 'month'
+ q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
+ cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
+ exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
+ var_from (Optional[datetime]): Filter for stories created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for stories created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ initialized_from (Optional[datetime]): Filter for stories initialized on or after this date. A story is initialized when it becomes visible with generated story data.
+ initialized_to (Optional[datetime]): Filter for stories initialized on or before this date. A story is initialized when it becomes visible with generated story data.
+ updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
+ updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
+ topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
+ category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
+ taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
+ source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
+ source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
+ min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
+ min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
+ person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
+ person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
+ company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
+ company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
+ company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
+ company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes of top mentioned companies (International Securities Identification Numbers). Returns stories where companies with these ISINs appear prominently. For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes on ticker listings of top mentioned companies (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. Returns stories where those companies appear prominently. For available company entities and their listing ISINs, consult the /companies endpoint.
+ country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
+ state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
+ max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
+ name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
+ positive_sentiment_from (Optional[float]): Filter stories with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ positive_sentiment_to (Optional[float]): Filter stories with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ neutral_sentiment_from (Optional[float]): Filter stories with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ neutral_sentiment_to (Optional[float]): Filter stories with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ negative_sentiment_from (Optional[float]): Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ negative_sentiment_to (Optional[float]): Filter stories with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
+ time_zone (Optional[str]): IANA time zone used to align interval buckets (e.g. America/Chicago). Defaults to UTC. Applies to intervalArticleCounts.
+
+ Returns:
+ StatResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_ARTICLES
+ path = PATH_GET_STORY_COUNTS
params: Dict[str, Any] = {}
if q is not None:
params["q"] = q
- if title is not None:
- params["title"] = title
- if desc is not None:
- params["desc"] = desc
- if content is not None:
- params["content"] = content
- if summary is not None:
- params["summary"] = summary
- if url is not None:
- params["url"] = url
- if article_id is not None:
- params["articleId"] = article_id
+ if name is not None:
+ params["name"] = name
if cluster_id is not None:
params["clusterId"] = cluster_id
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
+ if exclude_cluster_id is not None:
+ params["excludeClusterId"] = exclude_cluster_id
if var_from is not None:
params["from"] = var_from
if to is not None:
params["to"] = to
- if add_date_from is not None:
- params["addDateFrom"] = add_date_from
- if add_date_to is not None:
- params["addDateTo"] = add_date_to
- if refresh_date_from is not None:
- params["refreshDateFrom"] = refresh_date_from
- if refresh_date_to is not None:
- params["refreshDateTo"] = refresh_date_to
- if medium is not None:
- params["medium"] = medium
+ if initialized_from is not None:
+ params["initializedFrom"] = initialized_from
+ if initialized_to is not None:
+ params["initializedTo"] = initialized_to
+ if updated_from is not None:
+ params["updatedFrom"] = updated_from
+ if updated_to is not None:
+ params["updatedTo"] = updated_to
+ if topic is not None:
+ params["topic"] = topic
+ if category is not None:
+ params["category"] = category
+ if taxonomy is not None:
+ params["taxonomy"] = taxonomy
if source is not None:
params["source"] = source
if source_group is not None:
params["sourceGroup"] = source_group
- if exclude_source_group is not None:
- params["excludeSourceGroup"] = exclude_source_group
- if exclude_source is not None:
- params["excludeSource"] = exclude_source
- if watchlist is not None:
- params["watchlist"] = watchlist
- if exclude_watchlist is not None:
- params["excludeWatchlist"] = exclude_watchlist
- if paywall is not None:
- params["paywall"] = paywall
- if byline is not None:
- params["byline"] = byline
- if author is not None:
- params["author"] = author
- if exclude_author is not None:
- params["excludeAuthor"] = exclude_author
- if journalist_id is not None:
- params["journalistId"] = journalist_id
- if exclude_journalist_id is not None:
- params["excludeJournalistId"] = exclude_journalist_id
- if language is not None:
- params["language"] = language
- if exclude_language is not None:
- params["excludeLanguage"] = exclude_language
- if search_translation is not None:
- params["searchTranslation"] = search_translation
- if label is not None:
- params["label"] = label
- if exclude_label is not None:
- params["excludeLabel"] = exclude_label
- if category is not None:
- params["category"] = category
- if exclude_category is not None:
- params["excludeCategory"] = exclude_category
- if topic is not None:
- params["topic"] = topic
- if exclude_topic is not None:
- params["excludeTopic"] = exclude_topic
- if link_to is not None:
- params["linkTo"] = link_to
- if show_reprints is not None:
- params["showReprints"] = show_reprints
- if reprint_group_id is not None:
- params["reprintGroupId"] = reprint_group_id
- if city is not None:
- params["city"] = city
- if exclude_city is not None:
- params["excludeCity"] = exclude_city
- if area is not None:
- params["area"] = area
- if state is not None:
- params["state"] = state
- if exclude_state is not None:
- params["excludeState"] = exclude_state
- if county is not None:
- params["county"] = county
- if exclude_county is not None:
- params["excludeCounty"] = exclude_county
- if locations_country is not None:
- params["locationsCountry"] = locations_country
- if country is not None:
- params["country"] = country
- if exclude_locations_country is not None:
- params["excludeLocationsCountry"] = exclude_locations_country
- if location is not None:
- params["location"] = location
- if lat is not None:
- params["lat"] = lat
- if lon is not None:
- params["lon"] = lon
- if max_distance is not None:
- params["maxDistance"] = max_distance
- if source_city is not None:
- params["sourceCity"] = source_city
- if exclude_source_city is not None:
- params["excludeSourceCity"] = exclude_source_city
- if source_county is not None:
- params["sourceCounty"] = source_county
- if exclude_source_county is not None:
- params["excludeSourceCounty"] = exclude_source_county
- if source_country is not None:
- params["sourceCountry"] = source_country
- if exclude_source_country is not None:
- params["excludeSourceCountry"] = exclude_source_country
- if source_state is not None:
- params["sourceState"] = source_state
- if exclude_source_state is not None:
- params["excludeSourceState"] = exclude_source_state
- if source_lat is not None:
- params["sourceLat"] = source_lat
- if source_lon is not None:
- params["sourceLon"] = source_lon
- if source_max_distance is not None:
- params["sourceMaxDistance"] = source_max_distance
+ if min_unique_sources is not None:
+ params["minUniqueSources"] = min_unique_sources
+ if min_source_diversity is not None:
+ params["minSourceDiversity"] = min_source_diversity
if person_wikidata_id is not None:
params["personWikidataId"] = person_wikidata_id
- if exclude_person_wikidata_id is not None:
- params["excludePersonWikidataId"] = exclude_person_wikidata_id
if person_name is not None:
params["personName"] = person_name
- if exclude_person_name is not None:
- params["excludePersonName"] = exclude_person_name
if company_id is not None:
params["companyId"] = company_id
- if exclude_company_id is not None:
- params["excludeCompanyId"] = exclude_company_id
if company_name is not None:
params["companyName"] = company_name
if company_domain is not None:
params["companyDomain"] = company_domain
- if exclude_company_domain is not None:
- params["excludeCompanyDomain"] = exclude_company_domain
if company_symbol is not None:
params["companySymbol"] = company_symbol
- if exclude_company_symbol is not None:
- params["excludeCompanySymbol"] = exclude_company_symbol
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if country is not None:
+ params["country"] = country
+ if state is not None:
+ params["state"] = state
+ if city is not None:
+ params["city"] = city
+ if area is not None:
+ params["area"] = area
+ if min_cluster_size is not None:
+ params["minClusterSize"] = min_cluster_size
+ if max_cluster_size is not None:
+ params["maxClusterSize"] = max_cluster_size
+ if name_exists is not None:
+ params["nameExists"] = name_exists
if positive_sentiment_from is not None:
params["positiveSentimentFrom"] = positive_sentiment_from
if positive_sentiment_to is not None:
@@ -2020,1354 +3888,1551 @@ async def search_articles_async(
params["negativeSentimentFrom"] = negative_sentiment_from
if negative_sentiment_to is not None:
params["negativeSentimentTo"] = negative_sentiment_to
- if taxonomy is not None:
- params["taxonomy"] = taxonomy
- if prefix_taxonomy is not None:
- params["prefixTaxonomy"] = prefix_taxonomy
- if show_highlighting is not None:
- params["showHighlighting"] = show_highlighting
- if highlight_fragment_size is not None:
- params["highlightFragmentSize"] = highlight_fragment_size
- if highlight_num_fragments is not None:
- params["highlightNumFragments"] = highlight_num_fragments
- if highlight_pre_tag is not None:
- params["highlightPreTag"] = highlight_pre_tag
- if highlight_post_tag is not None:
- params["highlightPostTag"] = highlight_post_tag
- if highlight_q is not None:
- params["highlightQ"] = highlight_q
+ if show_duplicates is not None:
+ params["showDuplicates"] = show_duplicates
+ if split_by is not None:
+ params["splitBy"] = split_by
+ if time_zone is not None:
+ params["timeZone"] = time_zone
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return StatResult.model_validate(resp.json())
+
+ # ----------------- get_story_history (async) ----------------- #
+ async def get_story_history(
+ self,
+ cluster_id: Optional[List[str]] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ sort_by: Optional[str] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ changelog_exists: Optional[bool] = None,
+ ) -> StoryHistoryResult:
+ """
+ Retrieve previous generated versions of stories and their evolving summaries, key points, questions, and changelogs.
+
+ Args:
+ cluster_id (Optional[List[str]]): Filter history records by story cluster ID. Multiple values are combined with OR.
+ var_from (Optional[datetime]): Filter for history records created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for history records created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ sort_by (Optional[str]): Field used to sort history records in descending order. Accepts createdAt or triggeredAt and defaults to createdAt.
+ Allowed values: 'createdAt', 'triggeredAt'
+ page (Optional[int]): Integer. Zero-based page number. From 0 to 10000. See the Pagination section for limitations.
+ size (Optional[int]): Integer. Number of stories results per page, from 0 to 100.
+ changelog_exists (Optional[bool]): Filter by whether a history record contains a changelog. Use true to require one or false to require its absence.
+
+ Returns:
+ StoryHistoryResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_STORY_HISTORY
+
+ params: Dict[str, Any] = {}
+ if cluster_id is not None:
+ params["clusterId"] = cluster_id
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if changelog_exists is not None:
+ params["changelogExists"] = changelog_exists
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return QuerySearchResult.model_validate(resp.json())
+ return StoryHistoryResult.model_validate(resp.json())
- # ----------------- search_companies (sync) ----------------- #
- def search_companies(
+ # ----------------- get_watchlist (async) ----------------- #
+ async def get_watchlist(self, id: int) -> WatchlistResult:
+ """
+ Retrieve a watchlist by ID. Only returns watchlists owned by the organization associated with the API key.
+
+ Args:
+ id (int): Parameter id (required)
+
+ Returns:
+ WatchlistResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_GET_WATCHLIST
+
+ # Replace path parameters
+ path = path.format(id=str(id))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return WatchlistResult.model_validate(resp.json())
+
+ # ----------------- list_contact_points (async) ----------------- #
+ async def list_contact_points(
self,
- id: Optional[List[str]] = None,
- symbol: Optional[List[str]] = None,
- domain: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exchange: Optional[List[str]] = None,
- num_employees_from: Optional[int] = None,
- num_employees_to: Optional[int] = None,
- ipo_from: Optional[datetime] = None,
- ipo_to: Optional[datetime] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- industry: Optional[str] = None,
- sector: Optional[str] = None,
- size: Optional[int] = None,
+ sort_by: ContactPointSortBy,
+ sort_order: str,
+ status: Optional[List[str]] = None,
+ type: Optional[List[str]] = None,
page: Optional[int] = None,
- ) -> CompanySearchResult:
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> ContactPointListResult:
"""
- Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
+ List contact points owned by the organization associated with the API key. Supports filtering by status and type. FASTN contact points are excluded from results.
Args:
- id (Optional[List[str]]): String Array. Filter by unique company identifiers. Multiple values create an OR filter.
- symbol (Optional[List[str]]): String Array. Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
- domain (Optional[List[str]]): String Array. Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
- country (Optional[List[str]]): String Array. Filter by company headquarters country. Multiple values create an OR filter.
- exchange (Optional[List[str]]): String Array. Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
- num_employees_from (Optional[int]): Integer. Filter for companies with at least this many employees.
- num_employees_to (Optional[int]): Integer. Filter for companies with no more than this many employees.
- ipo_from (Optional[datetime]): Date. Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
- ipo_to (Optional[datetime]): Date. Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
- q (Optional[str]): String. Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- industry (Optional[str]): String. Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- sector (Optional[str]): String. Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- size (Optional[int]): Integer. The number of companies to return per page in the paginated response.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ sort_by (ContactPointSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ status (Optional[List[str]]): Parameter status
+ Allowed values: 'ACTIVE', 'STOPPED', 'FAILING', 'ARCHIVED'
+ type (Optional[List[str]]): Parameter type
+ Allowed values: 'EMAIL', 'FASTN', 'WEBHOOK'
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- CompanySearchResult: The response
+ ContactPointListResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_COMPANIES
+ path = PATH_LIST_CONTACT_POINTS
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if symbol is not None:
- params["symbol"] = symbol
- if domain is not None:
- params["domain"] = domain
- if country is not None:
- params["country"] = country
- if exchange is not None:
- params["exchange"] = exchange
- if num_employees_from is not None:
- params["numEmployeesFrom"] = num_employees_from
- if num_employees_to is not None:
- params["numEmployeesTo"] = num_employees_to
- if ipo_from is not None:
- params["ipoFrom"] = ipo_from
- if ipo_to is not None:
- params["ipoTo"] = ipo_to
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if industry is not None:
- params["industry"] = industry
- if sector is not None:
- params["sector"] = sector
+ if status is not None:
+ params["status"] = status
+ if type is not None:
+ params["type"] = type
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
if size is not None:
params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return ContactPointListResult.model_validate(resp.json())
+
+ # ----------------- list_monitor_events_api (async) ----------------- #
+ async def list_monitor_events_api(
+ self,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ event_type: Optional[str] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorEventListResult:
+ """
+ List structured events emitted by a monitor. Events represent individual matches detected by the monitor (e.g. a news article matching the monitor's criteria) and are produced asynchronously as new content is ingested.
+
+ Args:
+ uuid (UUID): UUID of the monitor whose events to retrieve. (required)
+ sort_by (str): Required field used to sort events. Accepts createdAt, updatedAt, or eventDate. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ event_type (Optional[str]): Filter for events with this exact event type.
+ var_from (Optional[datetime]): Filter for events created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for events created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+
+ Returns:
+ MonitorEventListResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_LIST_MONITOR_EVENTS_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ if event_type is not None:
+ params["eventType"] = event_type
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return CompanySearchResult.model_validate(resp.json())
+ return MonitorEventListResult.model_validate(resp.json())
- # ----------------- search_companies (async) ----------------- #
- async def search_companies_async(
+ # ----------------- list_monitor_newsletters_api (async) ----------------- #
+ async def list_monitor_newsletters_api(
self,
- id: Optional[List[str]] = None,
- symbol: Optional[List[str]] = None,
- domain: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exchange: Optional[List[str]] = None,
- num_employees_from: Optional[int] = None,
- num_employees_to: Optional[int] = None,
- ipo_from: Optional[datetime] = None,
- ipo_to: Optional[datetime] = None,
- q: Optional[str] = None,
- name: Optional[str] = None,
- industry: Optional[str] = None,
- sector: Optional[str] = None,
- size: Optional[int] = None,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ title: Optional[str] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
page: Optional[int] = None,
- ) -> CompanySearchResult:
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorNewsletterListResult:
"""
- Async variant of search_companies. Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
+ List newsletters generated for a monitor. Newsletters bundle a monitor's recent events into a human-readable digest and are produced on the monitor's configured schedule.
Args:
- id (Optional[List[str]]): String Array. Filter by unique company identifiers. Multiple values create an OR filter.
- symbol (Optional[List[str]]): String Array. Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
- domain (Optional[List[str]]): String Array. Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
- country (Optional[List[str]]): String Array. Filter by company headquarters country. Multiple values create an OR filter.
- exchange (Optional[List[str]]): String Array. Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
- num_employees_from (Optional[int]): Integer. Filter for companies with at least this many employees.
- num_employees_to (Optional[int]): Integer. Filter for companies with no more than this many employees.
- ipo_from (Optional[datetime]): Date. Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
- ipo_to (Optional[datetime]): Date. Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
- q (Optional[str]): String. Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- industry (Optional[str]): String. Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- sector (Optional[str]): String. Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- size (Optional[int]): Integer. The number of companies to return per page in the paginated response.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ uuid (UUID): UUID of the monitor whose newsletters to retrieve. (required)
+ sort_by (str): Required field used to sort newsletters. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ title (Optional[str]): Filter by newsletter title using a case-insensitive partial match.
+ var_from (Optional[datetime]): Filter for newsletters created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for newsletters created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- CompanySearchResult: The response
+ MonitorNewsletterListResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_COMPANIES
+ path = PATH_LIST_MONITOR_NEWSLETTERS_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if symbol is not None:
- params["symbol"] = symbol
- if domain is not None:
- params["domain"] = domain
- if country is not None:
- params["country"] = country
- if exchange is not None:
- params["exchange"] = exchange
- if num_employees_from is not None:
- params["numEmployeesFrom"] = num_employees_from
- if num_employees_to is not None:
- params["numEmployeesTo"] = num_employees_to
- if ipo_from is not None:
- params["ipoFrom"] = ipo_from
- if ipo_to is not None:
- params["ipoTo"] = ipo_to
- if q is not None:
- params["q"] = q
- if name is not None:
- params["name"] = name
- if industry is not None:
- params["industry"] = industry
- if sector is not None:
- params["sector"] = sector
+ if title is not None:
+ params["title"] = title
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
if size is not None:
params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return MonitorNewsletterListResult.model_validate(resp.json())
+
+ # ----------------- list_monitor_summaries_api (async) ----------------- #
+ async def list_monitor_summaries_api(
+ self,
+ uuid: UUID,
+ sort_by: str,
+ sort_order: str,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorSummaryListResult:
+ """
+ List AI-generated summaries produced for a monitor as matching content is processed.
+
+ Args:
+ uuid (UUID): UUID of the monitor whose summaries to retrieve. (required)
+ sort_by (str): Required field used to sort summaries. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ var_from (Optional[datetime]): Filter for summaries created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for summaries created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
+
+ Returns:
+ MonitorSummaryListResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_LIST_MONITOR_SUMMARIES_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return CompanySearchResult.model_validate(resp.json())
+ return MonitorSummaryListResult.model_validate(resp.json())
- # ----------------- search_journalists (sync) ----------------- #
- def search_journalists(
+ # ----------------- list_monitors_api (async) ----------------- #
+ async def list_monitors_api(
self,
- id: Optional[List[str]] = None,
- q: Optional[str] = None,
+ sort_by: str,
+ sort_order: str,
+ uuid: Optional[List[UUID]] = None,
name: Optional[str] = None,
- twitter: Optional[str] = None,
- size: Optional[int] = None,
+ status: Optional[List[str]] = None,
+ classification_type: Optional[List[str]] = None,
page: Optional[int] = None,
- source: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- updated_at_from: Optional[datetime] = None,
- updated_at_to: Optional[datetime] = None,
- show_num_results: Optional[bool] = None,
- ) -> JournalistSearchResult:
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> MonitorListResult:
"""
- Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
+ List AI monitors for the organization associated with the API key. Excludes archived monitors by default. Use the status parameter to filter.
Args:
- id (Optional[List[str]]): String Array. Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
- q (Optional[str]): String. Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- twitter (Optional[str]): String. Filter journalists by their exact Twitter handle, without the @ symbol.
- size (Optional[int]): Integer. The number of journalists to return per page in the paginated response.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- source (Optional[List[str]]): String Array. Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- topic (Optional[List[str]]): String Array. Filter journalists by the top topics they cover. Topics are more specific themes that the article is about. Examples of topics: 'Economy', 'Real Estate', 'Cryptocurrency'. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
- category (Optional[List[str]]): String Array. Filter journalists by the top categories they cover. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
- label (Optional[List[str]]): String Array. Filter journalists by the most common label tagged to the articles they publish. This accepts labels like 'Opinion' or 'Pop Culture'. (Searches the topLabels data field.)
- min_monthly_posts (Optional[int]): Integer. Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
- max_monthly_posts (Optional[int]): Integer. Filter for journalists who publish no more than this many articles per month.
- country (Optional[List[str]]): String Array. Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
- updated_at_from (Optional[datetime]): Date. Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- updated_at_to (Optional[datetime]): Date. Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
- show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+ sort_by (str): Required field used to sort monitors. Accepts createdAt or updatedAt. (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ uuid (Optional[List[UUID]]): Filter by one or more monitor UUIDs. Multiple values are combined with OR.
+ name (Optional[str]): Filter by monitor name using a case-insensitive partial match.
+ status (Optional[List[str]]): Filter by status: DRAFT, ACTIVE, STOPPED, or ARCHIVED. When omitted, archived monitors are excluded.
+ Allowed values: 'DRAFT', 'ACTIVE', 'STOPPED', 'ARCHIVED'
+ classification_type (Optional[List[str]]): Filter by classification type: EVENT, MENTIONS, or TOPIC. Multiple values are combined with OR.
+ Allowed values: 'EVENT', 'MENTIONS', 'TOPIC'
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- JournalistSearchResult: The response
+ MonitorListResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_JOURNALISTS
+ path = PATH_LIST_MONITORS_API
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if q is not None:
- params["q"] = q
+ if uuid is not None:
+ params["uuid"] = uuid
if name is not None:
params["name"] = name
- if twitter is not None:
- params["twitter"] = twitter
- if size is not None:
- params["size"] = size
+ if status is not None:
+ params["status"] = status
+ if classification_type is not None:
+ params["classificationType"] = classification_type
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
- if source is not None:
- params["source"] = source
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if label is not None:
- params["label"] = label
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if updated_at_from is not None:
- params["updatedAtFrom"] = updated_at_from
- if updated_at_to is not None:
- params["updatedAtTo"] = updated_at_to
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return JournalistSearchResult.model_validate(resp.json())
+ return MonitorListResult.model_validate(resp.json())
- # ----------------- search_journalists (async) ----------------- #
- async def search_journalists_async(
+ # ----------------- list_source_groups (async) ----------------- #
+ async def list_source_groups(
self,
- id: Optional[List[str]] = None,
- q: Optional[str] = None,
+ sort_by: SourceGroupSortBy,
+ sort_order: str,
name: Optional[str] = None,
- twitter: Optional[str] = None,
- size: Optional[int] = None,
+ domain: Optional[str] = None,
page: Optional[int] = None,
- source: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- updated_at_from: Optional[datetime] = None,
- updated_at_to: Optional[datetime] = None,
- show_num_results: Optional[bool] = None,
- ) -> JournalistSearchResult:
+ size: Optional[int] = None,
+ nulls: Optional[str] = None,
+ ) -> SourceGroupListResult:
"""
- Async variant of search_journalists. Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
+ List source groups owned by the organization associated with the API key, as well as publicly visible source groups. Supports filtering by name and domain.
Args:
- id (Optional[List[str]]): String Array. Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
- q (Optional[str]): String. Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- twitter (Optional[str]): String. Filter journalists by their exact Twitter handle, without the @ symbol.
- size (Optional[int]): Integer. The number of journalists to return per page in the paginated response.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- source (Optional[List[str]]): String Array. Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- topic (Optional[List[str]]): String Array. Filter journalists by the top topics they cover. Topics are more specific themes that the article is about. Examples of topics: 'Economy', 'Real Estate', 'Cryptocurrency'. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
- category (Optional[List[str]]): String Array. Filter journalists by the top categories they cover. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
- label (Optional[List[str]]): String Array. Filter journalists by the most common label tagged to the articles they publish. This accepts labels like 'Opinion' or 'Pop Culture'. (Searches the topLabels data field.)
- min_monthly_posts (Optional[int]): Integer. Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
- max_monthly_posts (Optional[int]): Integer. Filter for journalists who publish no more than this many articles per month.
- country (Optional[List[str]]): String Array. Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
- updated_at_from (Optional[datetime]): Date. Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- updated_at_to (Optional[datetime]): Date. Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
- show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+ sort_by (SourceGroupSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ name (Optional[str]): Parameter name
+ domain (Optional[str]): Parameter domain
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- JournalistSearchResult: The response
+ SourceGroupListResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_JOURNALISTS
+ path = PATH_LIST_SOURCE_GROUPS
params: Dict[str, Any] = {}
- if id is not None:
- params["id"] = id
- if q is not None:
- params["q"] = q
if name is not None:
params["name"] = name
- if twitter is not None:
- params["twitter"] = twitter
- if size is not None:
- params["size"] = size
+ if domain is not None:
+ params["domain"] = domain
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
- if source is not None:
- params["source"] = source
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if label is not None:
- params["label"] = label
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if updated_at_from is not None:
- params["updatedAtFrom"] = updated_at_from
- if updated_at_to is not None:
- params["updatedAtTo"] = updated_at_to
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
+ if size is not None:
+ params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return JournalistSearchResult.model_validate(resp.json())
+ return SourceGroupListResult.model_validate(resp.json())
- # ----------------- search_people (sync) ----------------- #
- def search_people(
+ # ----------------- list_watchlists (async) ----------------- #
+ async def list_watchlists(
self,
+ sort_by: WatchlistSortBy,
+ sort_order: str,
name: Optional[str] = None,
- wikidata_id: Optional[List[str]] = None,
- occupation_id: Optional[List[str]] = None,
- occupation_label: Optional[str] = None,
page: Optional[int] = None,
size: Optional[int] = None,
- ) -> PeopleSearchResult:
+ nulls: Optional[str] = None,
+ ) -> WatchlistListResult:
"""
- Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
+ List watchlists owned by the organization associated with the API key, as well as publicly visible watchlists. Supports filtering by name.
Args:
- name (Optional[str]): String. Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- wikidata_id (Optional[List[str]]): String Array. Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
- occupation_id (Optional[List[str]]): String Array. Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
- occupation_label (Optional[str]): String. Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of people to return per page in the paginated response.
+ sort_by (WatchlistSortBy): Parameter sort_by (required)
+ sort_order (str): Required direction used to sort results. Accepts asc or desc. (required)
+ Allowed values: 'asc', 'desc'
+ name (Optional[str]): Filter watchlists by name (case-insensitive, partial match)
+ page (Optional[int]): Zero-based page number to retrieve. Defaults to 0.
+ size (Optional[int]): Number of results per page. Must be between 1 and 100; defaults to 10.
+ nulls (Optional[str]): Placement of null values in sorted results. Accepts first or last.
+ Allowed values: 'first', 'last'
Returns:
- PeopleSearchResult: The response
+ WatchlistListResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_PEOPLE
+ path = PATH_LIST_WATCHLISTS
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
if name is not None:
params["name"] = name
- if wikidata_id is not None:
- params["wikidataId"] = wikidata_id
- if occupation_id is not None:
- params["occupationId"] = occupation_id
- if occupation_label is not None:
- params["occupationLabel"] = occupation_label
+ if sort_by is not None:
+ params["sortBy"] = sort_by
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
+ if sort_order is not None:
+ params["sortOrder"] = sort_order
+ if nulls is not None:
+ params["nulls"] = nulls
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return PeopleSearchResult.model_validate(resp.json())
+ return WatchlistListResult.model_validate(resp.json())
- # ----------------- search_people (async) ----------------- #
- async def search_people_async(
- self,
- name: Optional[str] = None,
- wikidata_id: Optional[List[str]] = None,
- occupation_id: Optional[List[str]] = None,
- occupation_label: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> PeopleSearchResult:
+ # ----------------- pause_monitor_api (async) ----------------- #
+ async def pause_monitor_api(self, uuid: UUID) -> MonitorResult:
"""
- Async variant of search_people. Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
+ Pause a monitor, stopping its processing pipeline.
Args:
- name (Optional[str]): String. Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- wikidata_id (Optional[List[str]]): String Array. Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
- occupation_id (Optional[List[str]]): String Array. Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
- occupation_label (Optional[str]): String. Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of people to return per page in the paginated response.
+ uuid (UUID): UUID of the monitor to pause. (required)
Returns:
- PeopleSearchResult: The response
+ MonitorResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_PEOPLE
+ path = PATH_PAUSE_MONITOR_API
+
+ # Replace path parameters
+ path = path.format(uuid=str(uuid))
+
+ params: Dict[str, Any] = {}
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("POST", path, params=params)
+ resp.raise_for_status()
+ return MonitorResult.model_validate(resp.json())
+
+ # ----------------- resolve_source_groups (async) ----------------- #
+ async def resolve_source_groups(
+ self, name: Optional[List[str]] = None
+ ) -> SourceGroupResolveResult:
+ """
+ Resolve source groups by name. For each name, returns the organization's private source group if one exists, otherwise falls back to the matching public source group.
+
+ Args:
+ name (Optional[List[str]]): Source group names to resolve (max 100)
+
+ Returns:
+ SourceGroupResolveResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_RESOLVE_SOURCE_GROUPS
params: Dict[str, Any] = {}
if name is not None:
params["name"] = name
- if wikidata_id is not None:
- params["wikidataId"] = wikidata_id
- if occupation_id is not None:
- params["occupationId"] = occupation_id
- if occupation_label is not None:
- params["occupationLabel"] = occupation_label
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return PeopleSearchResult.model_validate(resp.json())
+ return SourceGroupResolveResult.model_validate(resp.json())
+
+ # ----------------- resolve_watchlists (async) ----------------- #
+ async def resolve_watchlists(
+ self, name: Optional[List[str]] = None
+ ) -> WatchlistResolveResult:
+ """
+ Resolve watchlists by name. For each name, returns the organization's private watchlist if one exists, otherwise falls back to the matching public watchlist.
+
+ Args:
+ name (Optional[List[str]]): Watchlist names to resolve (max 100)
+
+ Returns:
+ WatchlistResolveResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_RESOLVE_WATCHLISTS
+
+ params: Dict[str, Any] = {}
+ if name is not None:
+ params["name"] = name
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return WatchlistResolveResult.model_validate(resp.json())
- # ----------------- search_sources (sync) ----------------- #
- def search_sources(
+ # ----------------- search_articles (async) ----------------- #
+ async def search_articles(
self,
- domain: Optional[List[str]] = None,
- name: Optional[str] = None,
- source_group: Optional[str] = None,
- sort_by: Optional[SortBy] = None,
+ q: Optional[str] = None,
+ title: Optional[str] = None,
+ desc: Optional[str] = None,
+ content: Optional[str] = None,
+ summary: Optional[str] = None,
+ url: Optional[str] = None,
+ article_id: Optional[List[str]] = None,
+ cluster_id: Optional[List[str]] = None,
+ sort_by: Optional[AllEndpointSortBy] = None,
page: Optional[int] = None,
size: Optional[int] = None,
- min_monthly_visits: Optional[int] = None,
- max_monthly_visits: Optional[int] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
+ var_from: Optional[datetime] = None,
+ to: Optional[datetime] = None,
+ add_date_from: Optional[datetime] = None,
+ add_date_to: Optional[datetime] = None,
+ refresh_date_from: Optional[datetime] = None,
+ refresh_date_to: Optional[datetime] = None,
+ medium: Optional[List[str]] = None,
+ source: Optional[List[str]] = None,
+ source_group: Optional[List[str]] = None,
+ exclude_source_group: Optional[List[str]] = None,
+ exclude_source: Optional[List[str]] = None,
+ watchlist: Optional[List[str]] = None,
+ exclude_watchlist: Optional[List[str]] = None,
+ paywall: Optional[bool] = None,
+ byline: Optional[List[str]] = None,
+ author: Optional[List[str]] = None,
+ exclude_author: Optional[List[str]] = None,
+ journalist_id: Optional[List[str]] = None,
+ exclude_journalist_id: Optional[List[str]] = None,
+ language: Optional[List[str]] = None,
+ exclude_language: Optional[List[str]] = None,
+ search_translation: Optional[bool] = None,
+ label: Optional[List[str]] = None,
+ exclude_label: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ exclude_category: Optional[List[str]] = None,
+ topic: Optional[List[str]] = None,
+ exclude_topic: Optional[List[str]] = None,
+ link_to: Optional[str] = None,
+ show_reprints: Optional[bool] = None,
+ reprint_group_id: Optional[str] = None,
+ city: Optional[List[str]] = None,
+ exclude_city: Optional[List[str]] = None,
+ area: Optional[List[str]] = None,
+ state: Optional[List[str]] = None,
+ exclude_state: Optional[List[str]] = None,
+ county: Optional[List[str]] = None,
+ exclude_county: Optional[List[str]] = None,
+ locations_country: Optional[List[str]] = None,
country: Optional[List[str]] = None,
+ exclude_locations_country: Optional[List[str]] = None,
+ location: Optional[List[str]] = None,
+ lat: Optional[float] = None,
+ lon: Optional[float] = None,
+ max_distance: Optional[float] = None,
+ source_city: Optional[List[str]] = None,
+ exclude_source_city: Optional[List[str]] = None,
+ source_county: Optional[List[str]] = None,
+ exclude_source_county: Optional[List[str]] = None,
source_country: Optional[List[str]] = None,
+ exclude_source_country: Optional[List[str]] = None,
source_state: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- source_city: Optional[List[str]] = None,
+ exclude_source_state: Optional[List[str]] = None,
source_lat: Optional[float] = None,
source_lon: Optional[float] = None,
source_max_distance: Optional[float] = None,
- category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- show_subdomains: Optional[bool] = None,
+ person_wikidata_id: Optional[List[str]] = None,
+ exclude_person_wikidata_id: Optional[List[str]] = None,
+ person_name: Optional[List[str]] = None,
+ exclude_person_name: Optional[List[str]] = None,
+ company_id: Optional[List[str]] = None,
+ exclude_company_id: Optional[List[str]] = None,
+ company_name: Optional[str] = None,
+ company_domain: Optional[List[str]] = None,
+ exclude_company_domain: Optional[List[str]] = None,
+ company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_isin: Optional[List[str]] = None,
+ exclude_company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_symbol: Optional[List[str]] = None,
show_num_results: Optional[bool] = None,
- ) -> SourceSearchResult:
+ positive_sentiment_from: Optional[float] = None,
+ positive_sentiment_to: Optional[float] = None,
+ neutral_sentiment_from: Optional[float] = None,
+ neutral_sentiment_to: Optional[float] = None,
+ negative_sentiment_from: Optional[float] = None,
+ negative_sentiment_to: Optional[float] = None,
+ taxonomy: Optional[List[str]] = None,
+ prefix_taxonomy: Optional[str] = None,
+ show_highlighting: Optional[bool] = None,
+ highlight_fragment_size: Optional[int] = None,
+ highlight_num_fragments: Optional[int] = None,
+ highlight_pre_tag: Optional[str] = None,
+ highlight_post_tag: Optional[str] = None,
+ highlight_q: Optional[str] = None,
+ ) -> QuerySearchResult:
"""
- Search and filter the 200,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
+ Search and filter all news articles available via the Perigon API. The result includes a list of individual articles that were matched to your specific criteria.
Args:
- domain (Optional[List[str]]): String Array. Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
- name (Optional[str]): String. Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- source_group (Optional[str]): String. Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
- sort_by (Optional[SortBy]): String. Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
+ q (Optional[str]): String. Primary search query for filtering articles based on their title, description, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ title (Optional[str]): String. Search specifically within article headlines/titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
+ desc (Optional[str]): String. Search within article description fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
+ content (Optional[str]): String. Search within the full article body content. Supports Boolean logic, exact phrase matching with quotes, and wildcards for comprehensive content searching.
+ summary (Optional[str]): String. Search within article summary fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
+ url (Optional[str]): String. Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel.
+ article_id (Optional[List[str]]): String Array. Retrieve specific news articles by their unique article identifiers. Multiple IDs can be provided to return a collection of specific articles.
+ cluster_id (Optional[List[str]]): String Array. Filter results to only show content within a specific related content cluster. Returns articles grouped together as part of Perigon Stories based on topic relevance.
+ sort_by (Optional[AllEndpointSortBy]): String. Determines the article sorting order. Options include relevance (default), date/pubDate (newest publication date first), reverseDate (oldest publication date first), addDate (newest ingestion date first), reverseAddDate (oldest ingestion date first), and refreshDate (most recently updated in system first, often identical to addDate).
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of sources to return per page in the paginated response.
- min_monthly_visits (Optional[int]): Integer. Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
- max_monthly_visits (Optional[int]): Integer. Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
- min_monthly_posts (Optional[int]): Integer. Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
- max_monthly_posts (Optional[int]): Integer. Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
- country (Optional[List[str]]): String Array. Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_country (Optional[List[str]]): String Array. Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_state (Optional[List[str]]): String Array. Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
- source_county (Optional[List[str]]): String Array. Filter for local publications based in specific counties. Multiple values create an OR filter.
- source_city (Optional[List[str]]): String Array. Filter for local publications based in specific cities. Multiple values create an OR filter.
- source_lat (Optional[float]): Float. Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
- source_lon (Optional[float]): Float. Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
- source_max_distance (Optional[float]): Float. Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
- category (Optional[List[str]]): String Array. Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
- topic (Optional[List[str]]): String Array. Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
- label (Optional[List[str]]): String Array. Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
- paywall (Optional[bool]): Boolean. Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
- show_subdomains (Optional[bool]): Boolean. Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
- show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+ size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
+ var_from (Optional[datetime]): Date. Filter for articles published after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ to (Optional[datetime]): Date. Filter for articles published before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ add_date_from (Optional[datetime]): Date. Filter for articles added to Perigon's system after this date. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
+ add_date_to (Optional[datetime]): Date. Filter for articles added to Perigon's system before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ refresh_date_from (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system after this date. In most cases yields similar results to addDateFrom but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
+ refresh_date_to (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system before this date. In most cases yields similar results to addDateTo but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
+ medium (Optional[List[str]]): String Array. Filter articles by their primary medium type. Accepts Article for written content or Video for video-based stories. Multiple values create an OR filter.
+ source (Optional[List[str]]): String Array. Filter articles by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
+ source_group (Optional[List[str]]): String Array. Filter articles using Perigon's curated publisher bundles (e.g., top100, top25crypto). Multiple values create an OR filter to include articles from any of the specified bundles.
+ exclude_source_group (Optional[List[str]]): String Array. Exclude articles from specified Perigon source groups. Multiple values create an AND-exclude filter, removing content from publishers in any of the specified bundles (e.g., top10, top100).
+ exclude_source (Optional[List[str]]): String Array. Exclude articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an AND-exclude filter.
+ watchlist (Optional[List[str]]): String Array. Filter articles using watchlists of people and companies. Multiple values create an OR filter to include articles mentioning any entity from the specified watchlists.
+ exclude_watchlist (Optional[List[str]]): String Array. Exclude articles mentioning entities from specified watchlists. Multiple values create an AND-exclude filter, removing content mentioning any entity from the specified watchlists.
+ paywall (Optional[bool]): Boolean. Filter to show only results where the source has a paywall (true) or does not have a paywall (false).
+ byline (Optional[List[str]]): String Array. Filter articles by author bylines. Works as an exact match for each author name provided. Multiple values create an OR filter to find articles by any of the specified authors.
+ author (Optional[List[str]]): String Array. Filter articles by specific author names. Works as an exact match for each name. Multiple values create an OR filter to find articles by any of the specified authors.
+ exclude_author (Optional[List[str]]): String Array. Exclude articles written by specific authors. Any article with an author name matching an entry in this list will be omitted from results. Multiple values create an AND-exclude filter.
+ journalist_id (Optional[List[str]]): String Array. Filter by unique journalist identifiers which can be found through the Journalist API or in the matchedAuthors field. Multiple values create an OR filter.
+ exclude_journalist_id (Optional[List[str]]): String Array. Exclude articles written by specific journalists identified by their unique IDs. Multiple values create an AND-exclude filter.
+ language (Optional[List[str]]): String Array. Filter articles by their language using ISO-639 two-letter codes (e.g., en, es, fr). Multiple values create an OR filter.
+ exclude_language (Optional[List[str]]): String Array. Exclude articles in specific languages using ISO-639 two-letter codes. Multiple values create an AND-exclude filter.
+ search_translation (Optional[bool]): Boolean. Expand search to include translated content fields for non-English articles. When true, searches translated title, description, and content fields.
+ label (Optional[List[str]]): String Array. Filter articles by editorial labels such as Opinion, Paid-news, Non-news, Fact Check, or Press Release. Multiple values create an OR filter.
+ exclude_label (Optional[List[str]]): String Array. Exclude articles with specific editorial labels. Multiple values create an AND-exclude filter, removing all content with any of these labels.
+ category (Optional[List[str]]): String Array. Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles.
+ exclude_category (Optional[List[str]]): String Array. Exclude articles with specific categories. Multiple values create an AND-exclude filter, removing all content with any of these categories.
+ topic (Optional[List[str]]): String Array. Filter by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and articles can have multiple topics. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
+ exclude_topic (Optional[List[str]]): String Array. Exclude articles with specific topics. Multiple values create an AND-exclude filter, removing all content with any of these topics.
+ link_to (Optional[str]): String. Returns only articles that contain links to the specified URL pattern. Matches against the 'links' field in article responses.
+ show_reprints (Optional[bool]): Boolean. Controls whether to include reprinted content in results. When true (default), shows syndicated articles from wire services like AP or Reuters that appear on multiple sites.
+ reprint_group_id (Optional[str]): String. Returns all articles in a specific reprint group, including the original article and all its known reprints. Use when you want to see all versions of the same content.
+ city (Optional[List[str]]): String Array. Filters articles where a specified city plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the urban area in question. If multiple parameters are passed, they will be applied as OR operations.
+ exclude_city (Optional[List[str]]): String Array. A list of cities to exclude from the results. Articles that are associated with any of the specified cities will be filtered out.
+ area (Optional[List[str]]): String Array. Filters articles where a specified area, such as a neighborhood, borough, or district, plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the area in question. If multiple parameters are passed, they will be applied as OR operations.
+ state (Optional[List[str]]): String Array. Filters articles where a specified state plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the state in question. If multiple parameters are passed, they will be applied as OR operations.
+ exclude_state (Optional[List[str]]): String Array. A list of states to exclude. Articles that include, or are associated with, any of the states provided here will be filtered out. This is especially useful if you want to ignore news tied to certain geographical areas (e.g., US states).
+ county (Optional[List[str]]): String Array. A list of counties to include (or specify) in the search results. This field filters the returned articles based on the county associated with the event or news. Only articles tagged with one of these counties will be included.
+ exclude_county (Optional[List[str]]): String Array. Excludes articles from specific counties or administrative divisions in the search results. Accepts either a single county name or a list of county names. County names should match the format used in article metadata (e.g., 'Los Angeles County', 'Cook County'). This parameter allows for more granular geographic filter
+ locations_country (Optional[List[str]]): String Array. Filters articles where a specified country plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the country in question. If multiple parameters are passed, they will be applied as OR operations.
+ country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
+ exclude_locations_country (Optional[List[str]]): String Array. Excludes articles where a specified country plays a central role in the content, ensuring results are not deeply relevant to the country in question. If multiple parameters are passed, they will be applied as AND operations, excluding articles relevant to any of the specified countries.
+ location (Optional[List[str]]): String Array. Return all articles that have the specified location. Location attributes are delimited by ':' between key and value, and '::' between attributes. Example: 'city:New York::state:NY'.
+ lat (Optional[float]): Float. Latitude of the center point to search places
+ lon (Optional[float]): Float. Longitude of the center point to search places
+ max_distance (Optional[float]): Float. Maximum distance (in km) from starting point to search articles by tagged places
+ source_city (Optional[List[str]]): String Array. Find articles published by sources that are located within a given city.
+ exclude_source_city (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified cities.
+ source_county (Optional[List[str]]): String Array. Find articles published by sources that are located within a given county.
+ exclude_source_county (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified counties.
+ source_country (Optional[List[str]]): String Array. Find articles published by sources that are located within a given country. Must be 2 character country code (i.e. us, gb, etc).
+ exclude_source_country (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified countries. Must be 2 character country codes (e.g., us, gb).
+ source_state (Optional[List[str]]): String Array. Find articles published by sources that are located within a given state.
+ exclude_source_state (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified states.
+ source_lat (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
+ source_lon (Optional[float]): Float. Longitude of the center point to search articles created by local publications.
+ source_max_distance (Optional[float]): Float. Maximum distance from starting point to search articles created by local publications.
+ person_wikidata_id (Optional[List[str]]): String Array. Filter articles by Wikidata IDs of mentioned people. Refer to the /people endpoint for a complete list of tracked individuals.
+ exclude_person_wikidata_id (Optional[List[str]]): String Array. Exclude articles mentioning people with specific Wikidata IDs. Creates an AND-exclude filter to remove content about these individuals. Uses precise identifiers to avoid name ambiguity.
+ person_name (Optional[List[str]]): String Array. Filter articles by exact person name matches. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
+ exclude_person_name (Optional[List[str]]): String Array. Exclude articles mentioning specific people by name. Creates an AND-exclude filter to remove content about these individuals.
+ company_id (Optional[List[str]]): String Array. Filter articles by company identifiers. For a complete list of tracked companies, refer to the /companies endpoint.
+ exclude_company_id (Optional[List[str]]): String Array. Exclude articles mentioning companies with specific identifiers. Creates an AND-exclude filter to remove content about these corporate entities.
+ company_name (Optional[str]): String. Filter articles by company name mentions. Performs an exact match on company names.
+ company_domain (Optional[List[str]]): String Array. Filter articles by company domains (e.g., apple.com). For available company entities, consult the /companies endpoint.
+ exclude_company_domain (Optional[List[str]]): String Array. Exclude articles related to companies with specific domains. Creates an AND-exclude filter to remove content about these companies.
+ company_symbol (Optional[List[str]]): String Array. Filter articles by company stock symbols. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter articles by company ISIN codes (International Securities Identification Numbers). For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter articles by ISIN codes on company ticker listings (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
+ exclude_company_isin (Optional[List[str]]): String Array. Exclude articles related to companies with specific ISIN codes. Creates an AND-exclude filter to remove content about these companies. For available company entities and their ISINs, consult the /companies endpoint.
+ exclude_company_symbol_isin (Optional[List[str]]): String Array. Exclude articles related to companies whose ticker listings have these ISIN codes (symbols.isin). Distinct from excludeCompanyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
+ exclude_company_symbol (Optional[List[str]]): String Array. A list of stock symbols (ticker symbols) that identify companies to be excluded. Articles related to companies using any of these symbols will be omitted, which is useful for targeting or avoiding specific public companies.
+ show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.
+ positive_sentiment_from (Optional[float]): Float. Filter articles with a positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ positive_sentiment_to (Optional[float]): Float. Filter articles with a positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ neutral_sentiment_from (Optional[float]): Float. Filter articles with a neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ neutral_sentiment_to (Optional[float]): Float. Filter articles with a neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ negative_sentiment_from (Optional[float]): Float. Filter articles with a negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ negative_sentiment_to (Optional[float]): Float. Filter articles with a negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ taxonomy (Optional[List[str]]): String Array. Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds. [Full list](https://docs.cloud.google.com/natural-language/docs/categories#version_2)
+ prefix_taxonomy (Optional[str]): String. Filters by Google Content Categories. This field will filter by the category prefix only. Example: prefixTaxonomy=/Finance
+ show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
+ highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
+ highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
+ highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
+ highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
+ highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
Returns:
- SourceSearchResult: The response
+ QuerySearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_SOURCES
+ path = PATH_SEARCH_ARTICLES
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
- if domain is not None:
- params["domain"] = domain
- if name is not None:
- params["name"] = name
+ if q is not None:
+ params["q"] = q
+ if title is not None:
+ params["title"] = title
+ if desc is not None:
+ params["desc"] = desc
+ if content is not None:
+ params["content"] = content
+ if summary is not None:
+ params["summary"] = summary
+ if url is not None:
+ params["url"] = url
+ if article_id is not None:
+ params["articleId"] = article_id
+ if cluster_id is not None:
+ params["clusterId"] = cluster_id
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if var_from is not None:
+ params["from"] = var_from
+ if to is not None:
+ params["to"] = to
+ if add_date_from is not None:
+ params["addDateFrom"] = add_date_from
+ if add_date_to is not None:
+ params["addDateTo"] = add_date_to
+ if refresh_date_from is not None:
+ params["refreshDateFrom"] = refresh_date_from
+ if refresh_date_to is not None:
+ params["refreshDateTo"] = refresh_date_to
+ if medium is not None:
+ params["medium"] = medium
+ if source is not None:
+ params["source"] = source
if source_group is not None:
params["sourceGroup"] = source_group
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if min_monthly_visits is not None:
- params["minMonthlyVisits"] = min_monthly_visits
- if max_monthly_visits is not None:
- params["maxMonthlyVisits"] = max_monthly_visits
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
+ if exclude_source_group is not None:
+ params["excludeSourceGroup"] = exclude_source_group
+ if exclude_source is not None:
+ params["excludeSource"] = exclude_source
+ if watchlist is not None:
+ params["watchlist"] = watchlist
+ if exclude_watchlist is not None:
+ params["excludeWatchlist"] = exclude_watchlist
+ if paywall is not None:
+ params["paywall"] = paywall
+ if byline is not None:
+ params["byline"] = byline
+ if author is not None:
+ params["author"] = author
+ if exclude_author is not None:
+ params["excludeAuthor"] = exclude_author
+ if journalist_id is not None:
+ params["journalistId"] = journalist_id
+ if exclude_journalist_id is not None:
+ params["excludeJournalistId"] = exclude_journalist_id
+ if language is not None:
+ params["language"] = language
+ if exclude_language is not None:
+ params["excludeLanguage"] = exclude_language
+ if search_translation is not None:
+ params["searchTranslation"] = search_translation
+ if label is not None:
+ params["label"] = label
+ if exclude_label is not None:
+ params["excludeLabel"] = exclude_label
+ if category is not None:
+ params["category"] = category
+ if exclude_category is not None:
+ params["excludeCategory"] = exclude_category
+ if topic is not None:
+ params["topic"] = topic
+ if exclude_topic is not None:
+ params["excludeTopic"] = exclude_topic
+ if link_to is not None:
+ params["linkTo"] = link_to
+ if show_reprints is not None:
+ params["showReprints"] = show_reprints
+ if reprint_group_id is not None:
+ params["reprintGroupId"] = reprint_group_id
+ if city is not None:
+ params["city"] = city
+ if exclude_city is not None:
+ params["excludeCity"] = exclude_city
+ if area is not None:
+ params["area"] = area
+ if state is not None:
+ params["state"] = state
+ if exclude_state is not None:
+ params["excludeState"] = exclude_state
+ if county is not None:
+ params["county"] = county
+ if exclude_county is not None:
+ params["excludeCounty"] = exclude_county
+ if locations_country is not None:
+ params["locationsCountry"] = locations_country
if country is not None:
params["country"] = country
+ if exclude_locations_country is not None:
+ params["excludeLocationsCountry"] = exclude_locations_country
+ if location is not None:
+ params["location"] = location
+ if lat is not None:
+ params["lat"] = lat
+ if lon is not None:
+ params["lon"] = lon
+ if max_distance is not None:
+ params["maxDistance"] = max_distance
+ if source_city is not None:
+ params["sourceCity"] = source_city
+ if exclude_source_city is not None:
+ params["excludeSourceCity"] = exclude_source_city
+ if source_county is not None:
+ params["sourceCounty"] = source_county
+ if exclude_source_county is not None:
+ params["excludeSourceCounty"] = exclude_source_county
if source_country is not None:
params["sourceCountry"] = source_country
+ if exclude_source_country is not None:
+ params["excludeSourceCountry"] = exclude_source_country
if source_state is not None:
params["sourceState"] = source_state
- if source_county is not None:
- params["sourceCounty"] = source_county
- if source_city is not None:
- params["sourceCity"] = source_city
+ if exclude_source_state is not None:
+ params["excludeSourceState"] = exclude_source_state
if source_lat is not None:
params["sourceLat"] = source_lat
if source_lon is not None:
params["sourceLon"] = source_lon
if source_max_distance is not None:
params["sourceMaxDistance"] = source_max_distance
- if category is not None:
- params["category"] = category
- if topic is not None:
- params["topic"] = topic
- if label is not None:
- params["label"] = label
- if paywall is not None:
- params["paywall"] = paywall
- if show_subdomains is not None:
- params["showSubdomains"] = show_subdomains
+ if person_wikidata_id is not None:
+ params["personWikidataId"] = person_wikidata_id
+ if exclude_person_wikidata_id is not None:
+ params["excludePersonWikidataId"] = exclude_person_wikidata_id
+ if person_name is not None:
+ params["personName"] = person_name
+ if exclude_person_name is not None:
+ params["excludePersonName"] = exclude_person_name
+ if company_id is not None:
+ params["companyId"] = company_id
+ if exclude_company_id is not None:
+ params["excludeCompanyId"] = exclude_company_id
+ if company_name is not None:
+ params["companyName"] = company_name
+ if company_domain is not None:
+ params["companyDomain"] = company_domain
+ if exclude_company_domain is not None:
+ params["excludeCompanyDomain"] = exclude_company_domain
+ if company_symbol is not None:
+ params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if exclude_company_isin is not None:
+ params["excludeCompanyIsin"] = exclude_company_isin
+ if exclude_company_symbol_isin is not None:
+ params["excludeCompanySymbolIsin"] = exclude_company_symbol_isin
+ if exclude_company_symbol is not None:
+ params["excludeCompanySymbol"] = exclude_company_symbol
if show_num_results is not None:
params["showNumResults"] = show_num_results
+ if positive_sentiment_from is not None:
+ params["positiveSentimentFrom"] = positive_sentiment_from
+ if positive_sentiment_to is not None:
+ params["positiveSentimentTo"] = positive_sentiment_to
+ if neutral_sentiment_from is not None:
+ params["neutralSentimentFrom"] = neutral_sentiment_from
+ if neutral_sentiment_to is not None:
+ params["neutralSentimentTo"] = neutral_sentiment_to
+ if negative_sentiment_from is not None:
+ params["negativeSentimentFrom"] = negative_sentiment_from
+ if negative_sentiment_to is not None:
+ params["negativeSentimentTo"] = negative_sentiment_to
+ if taxonomy is not None:
+ params["taxonomy"] = taxonomy
+ if prefix_taxonomy is not None:
+ params["prefixTaxonomy"] = prefix_taxonomy
+ if show_highlighting is not None:
+ params["showHighlighting"] = show_highlighting
+ if highlight_fragment_size is not None:
+ params["highlightFragmentSize"] = highlight_fragment_size
+ if highlight_num_fragments is not None:
+ params["highlightNumFragments"] = highlight_num_fragments
+ if highlight_pre_tag is not None:
+ params["highlightPreTag"] = highlight_pre_tag
+ if highlight_post_tag is not None:
+ params["highlightPostTag"] = highlight_post_tag
+ if highlight_q is not None:
+ params["highlightQ"] = highlight_q
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return SourceSearchResult.model_validate(resp.json())
+ return QuerySearchResult.model_validate(resp.json())
- # ----------------- search_sources (async) ----------------- #
- async def search_sources_async(
+ # ----------------- search_companies (async) ----------------- #
+ async def search_companies(
self,
+ id: Optional[List[str]] = None,
+ symbol: Optional[List[str]] = None,
+ symbol_isin: Optional[List[str]] = None,
domain: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ exchange: Optional[List[str]] = None,
+ num_employees_from: Optional[int] = None,
+ num_employees_to: Optional[int] = None,
+ ipo_from: Optional[datetime] = None,
+ ipo_to: Optional[datetime] = None,
+ q: Optional[str] = None,
name: Optional[str] = None,
- source_group: Optional[str] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
+ industry: Optional[str] = None,
+ sector: Optional[str] = None,
size: Optional[int] = None,
- min_monthly_visits: Optional[int] = None,
- max_monthly_visits: Optional[int] = None,
- min_monthly_posts: Optional[int] = None,
- max_monthly_posts: Optional[int] = None,
- country: Optional[List[str]] = None,
- source_country: Optional[List[str]] = None,
- source_state: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- source_city: Optional[List[str]] = None,
- source_lat: Optional[float] = None,
- source_lon: Optional[float] = None,
- source_max_distance: Optional[float] = None,
- category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- label: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- show_subdomains: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- ) -> SourceSearchResult:
+ page: Optional[int] = None,
+ ) -> CompanySearchResult:
"""
- Async variant of search_sources. Search and filter the 200,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
+ Browse or search for companies Perigon tracks using name, domain, ticker symbol, industry, and more. Supports Boolean search logic and filtering by metadata such as country, exchange, employee count, and IPO date.
Args:
- domain (Optional[List[str]]): String Array. Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
- name (Optional[str]): String. Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- source_group (Optional[str]): String. Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
- sort_by (Optional[SortBy]): String. Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
+ id (Optional[List[str]]): String Array. Filter by unique company identifiers. Multiple values create an OR filter.
+ symbol (Optional[List[str]]): String Array. Filter by company stock ticker symbols (e.g., AAPL, MSFT, GOOGL). Multiple values create an OR filter.
+ symbol_isin (Optional[List[str]]): String Array. Filter by ISIN codes on company ticker listings. Matches symbols.isin rather than the company-level ISIN. Multiple values create an OR filter.
+ domain (Optional[List[str]]): String Array. Filter by company domains or websites (e.g., apple.com, microsoft.com). Multiple values create an OR filter.
+ country (Optional[List[str]]): String Array. Filter by company headquarters country. Multiple values create an OR filter.
+ exchange (Optional[List[str]]): String Array. Filter by stock exchange where companies are listed (e.g., NASDAQ, NYSE). Multiple values create an OR filter.
+ num_employees_from (Optional[int]): Integer. Filter for companies with at least this many employees.
+ num_employees_to (Optional[int]): Integer. Filter for companies with no more than this many employees.
+ ipo_from (Optional[datetime]): Date. Filter for companies that went public on or after this date. Accepts ISO 8601 format (e.g., 2023-01-01T00:00:00) or yyyy-mm-dd format.
+ ipo_to (Optional[datetime]): Date. Filter for companies that went public on or before this date. Accepts ISO 8601 format (e.g., 2023-12-31T23:59:59) or yyyy-mm-dd format.
+ q (Optional[str]): String. Primary search query for filtering companies across name, alternative names, domains, and ticker symbols. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search within company names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ industry (Optional[str]): String. Filter by company industry classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ sector (Optional[str]): String. Filter by company sector classifications. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ size (Optional[int]): Integer. The number of companies to return per page in the paginated response.
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of sources to return per page in the paginated response.
- min_monthly_visits (Optional[int]): Integer. Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
- max_monthly_visits (Optional[int]): Integer. Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
- min_monthly_posts (Optional[int]): Integer. Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
- max_monthly_posts (Optional[int]): Integer. Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
- country (Optional[List[str]]): String Array. Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_country (Optional[List[str]]): String Array. Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
- source_state (Optional[List[str]]): String Array. Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
- source_county (Optional[List[str]]): String Array. Filter for local publications based in specific counties. Multiple values create an OR filter.
- source_city (Optional[List[str]]): String Array. Filter for local publications based in specific cities. Multiple values create an OR filter.
- source_lat (Optional[float]): Float. Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
- source_lon (Optional[float]): Float. Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
- source_max_distance (Optional[float]): Float. Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
- category (Optional[List[str]]): String Array. Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
- topic (Optional[List[str]]): String Array. Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
- label (Optional[List[str]]): String Array. Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
- paywall (Optional[bool]): Boolean. Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
- show_subdomains (Optional[bool]): Boolean. Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
- show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
Returns:
- SourceSearchResult: The response
+ CompanySearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_SOURCES
+ path = PATH_SEARCH_COMPANIES
params: Dict[str, Any] = {}
+ if id is not None:
+ params["id"] = id
+ if symbol is not None:
+ params["symbol"] = symbol
+ if symbol_isin is not None:
+ params["symbolIsin"] = symbol_isin
if domain is not None:
params["domain"] = domain
+ if country is not None:
+ params["country"] = country
+ if exchange is not None:
+ params["exchange"] = exchange
+ if num_employees_from is not None:
+ params["numEmployeesFrom"] = num_employees_from
+ if num_employees_to is not None:
+ params["numEmployeesTo"] = num_employees_to
+ if ipo_from is not None:
+ params["ipoFrom"] = ipo_from
+ if ipo_to is not None:
+ params["ipoTo"] = ipo_to
+ if q is not None:
+ params["q"] = q
if name is not None:
params["name"] = name
- if source_group is not None:
- params["sourceGroup"] = source_group
- if sort_by is not None:
- params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
+ if industry is not None:
+ params["industry"] = industry
+ if sector is not None:
+ params["sector"] = sector
if size is not None:
params["size"] = size
- if min_monthly_visits is not None:
- params["minMonthlyVisits"] = min_monthly_visits
- if max_monthly_visits is not None:
- params["maxMonthlyVisits"] = max_monthly_visits
- if min_monthly_posts is not None:
- params["minMonthlyPosts"] = min_monthly_posts
- if max_monthly_posts is not None:
- params["maxMonthlyPosts"] = max_monthly_posts
- if country is not None:
- params["country"] = country
- if source_country is not None:
- params["sourceCountry"] = source_country
- if source_state is not None:
- params["sourceState"] = source_state
- if source_county is not None:
- params["sourceCounty"] = source_county
- if source_city is not None:
- params["sourceCity"] = source_city
- if source_lat is not None:
- params["sourceLat"] = source_lat
- if source_lon is not None:
- params["sourceLon"] = source_lon
- if source_max_distance is not None:
- params["sourceMaxDistance"] = source_max_distance
- if category is not None:
- params["category"] = category
- if topic is not None:
- params["topic"] = topic
- if label is not None:
- params["label"] = label
- if paywall is not None:
- params["paywall"] = paywall
- if show_subdomains is not None:
- params["showSubdomains"] = show_subdomains
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
+ if page is not None:
+ params["page"] = page
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return SourceSearchResult.model_validate(resp.json())
+ return CompanySearchResult.model_validate(resp.json())
- # ----------------- search_stories (sync) ----------------- #
- def search_stories(
+ # ----------------- search_journalists (async) ----------------- #
+ async def search_journalists(
self,
+ id: Optional[List[str]] = None,
q: Optional[str] = None,
name: Optional[str] = None,
- cluster_id: Optional[List[str]] = None,
- exclude_cluster_id: Optional[List[str]] = None,
- sort_by: Optional[SortBy] = None,
- page: Optional[int] = None,
+ twitter: Optional[str] = None,
+ sort_by: Optional[JournalistsSortBy] = None,
size: Optional[int] = None,
- var_from: Optional[datetime] = None,
- to: Optional[datetime] = None,
- initialized_from: Optional[datetime] = None,
- initialized_to: Optional[datetime] = None,
- updated_from: Optional[datetime] = None,
- updated_to: Optional[datetime] = None,
+ page: Optional[int] = None,
+ source: Optional[List[str]] = None,
topic: Optional[List[str]] = None,
category: Optional[List[str]] = None,
- taxonomy: Optional[List[str]] = None,
- source: Optional[List[str]] = None,
- source_group: Optional[List[str]] = None,
- min_unique_sources: Optional[int] = None,
- min_source_diversity: Optional[float] = None,
- person_wikidata_id: Optional[List[str]] = None,
- person_name: Optional[str] = None,
- company_id: Optional[List[str]] = None,
- company_name: Optional[str] = None,
- company_domain: Optional[List[str]] = None,
- company_symbol: Optional[List[str]] = None,
+ label: Optional[List[str]] = None,
+ min_monthly_posts: Optional[int] = None,
+ max_monthly_posts: Optional[int] = None,
country: Optional[List[str]] = None,
- state: Optional[List[str]] = None,
- city: Optional[List[str]] = None,
- area: Optional[List[str]] = None,
- min_cluster_size: Optional[int] = None,
- max_cluster_size: Optional[int] = None,
- name_exists: Optional[bool] = None,
- positive_sentiment_from: Optional[float] = None,
- positive_sentiment_to: Optional[float] = None,
- neutral_sentiment_from: Optional[float] = None,
- neutral_sentiment_to: Optional[float] = None,
- negative_sentiment_from: Optional[float] = None,
- negative_sentiment_to: Optional[float] = None,
- show_story_page_info: Optional[bool] = None,
+ location_country: Optional[List[str]] = None,
+ location_state: Optional[List[str]] = None,
+ location_county: Optional[List[str]] = None,
+ location_city: Optional[List[str]] = None,
+ location_area: Optional[List[str]] = None,
+ updated_at_from: Optional[datetime] = None,
+ updated_at_to: Optional[datetime] = None,
show_num_results: Optional[bool] = None,
- show_duplicates: Optional[bool] = None,
- show_highlighting: Optional[bool] = None,
- highlight_fragment_size: Optional[int] = None,
- highlight_num_fragments: Optional[int] = None,
- highlight_pre_tag: Optional[str] = None,
- highlight_post_tag: Optional[str] = None,
- highlight_q: Optional[str] = None,
- expand_articles: Optional[bool] = None,
- ) -> StorySearchResult:
+ ) -> JournalistSearchResult:
"""
- Track evolving narratives and top news headlines with the '**Stories**' endpoint β it returns structured clusters of related articles with summaries, key points, sentiment scores, and metadata on top people, companies, topics, countries, and categories. Use it to monitor how news stories develop over time, who and what they impact, and where they're gaining traction globally. Each story represents a cluster of related articles grouped during processing. Articles are assigned to a single story, identified by the `clusterId` field. Once a story reaches five articles, a story title is automatically generated based on its content. Stories update as new coverage is processed and key details evolve. To fetch all articles within a specific story, query the `/articles/all` endpoint using the `clusterId` parameter.
+ Search journalists using broad search attributes. Our database contains over 230,000 journalists from around the world and is refreshed frequently.
Args:
- q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
- cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
- exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
- sort_by (Optional[SortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
+ id (Optional[List[str]]): String Array. Filter by unique journalist identifiers. Multiple values create an OR filter to find journalists matching any of the specified IDs.
+ q (Optional[str]): String. Primary search query for filtering journalists based on their name, title, and Twitter bio. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search specifically within journalist names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ twitter (Optional[str]): String. Filter journalists by their exact Twitter handle, without the @ symbol.
+ sort_by (Optional[JournalistsSortBy]): String. Sort journalists from highest to lowest by avgMonthlyPosts, linkedinConnections, or linkedinFollowers. When omitted, results are sorted by relevance.
+ size (Optional[int]): Integer. The number of journalists to return per page in the paginated response.
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59
- initialized_from (Optional[datetime]): Date. Filter for stories created after this date. Alternative parameter for filtering by story creation date.
- initialized_to (Optional[datetime]): Date. Filter for stories created before this date. Alternative parameter for filtering by story creation date.
- updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
- updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
- topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
- category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
- taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
- source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
- source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
- min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
- min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
- person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
- person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
- company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
- company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
- company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
- company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
- country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
- state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
- max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
- name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
- positive_sentiment_from (Optional[float]): Float. Filter articles with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- positive_sentiment_to (Optional[float]): Float. Filter articles with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- neutral_sentiment_from (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- neutral_sentiment_to (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- negative_sentiment_from (Optional[float]): Float. Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- negative_sentiment_to (Optional[float]): Float. Filter articles with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- show_story_page_info (Optional[bool]): Boolean.
- show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
- show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
- show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
- highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
- highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
- highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
- highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
- highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
- expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
+ source (Optional[List[str]]): String Array. Filter journalists by the publisher domains they write for. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
+ topic (Optional[List[str]]): String Array. Filter journalists by the top topics they cover. Topics are more specific themes that the article is about. Examples of topics: 'Economy', 'Real Estate', 'Cryptocurrency'. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
+ category (Optional[List[str]]): String Array. Filter journalists by the top categories they cover. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. (Searches inside the topCategories data field.)
+ label (Optional[List[str]]): String Array. Filter journalists by the most common label tagged to the articles they publish. This accepts labels like 'Opinion' or 'Pop Culture'. (Searches the topLabels data field.)
+ min_monthly_posts (Optional[int]): Integer. Filter for journalists who publish at least this many articles per month. Used to identify more active journalists.
+ max_monthly_posts (Optional[int]): Integer. Filter for journalists who publish no more than this many articles per month.
+ country (Optional[List[str]]): String Array. Filter journalists by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). Multiple values create an OR filter.
+ location_country (Optional[List[str]]): String Array. Filter journalists by their profile location country (`locations.country`). Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). `uk` is accepted as an alias for `gb`. Distinct from `country`, which filters reporting focus (`topCountries`). Multiple values create an OR filter.
+ location_state (Optional[List[str]]): String Array. Filter journalists by their profile location state or region (`locations.state`). For US locations this is the two-letter state code (e.g., NY, CA). Multiple values create an OR filter.
+ location_county (Optional[List[str]]): String Array. Filter journalists by their profile location county (`locations.county`). County names should match stored metadata (e.g., 'Los Angeles County', 'Cook County'). Multiple values create an OR filter.
+ location_city (Optional[List[str]]): String Array. Filter journalists by their profile location city (`locations.city`). Multiple values create an OR filter.
+ location_area (Optional[List[str]]): String Array. Filter journalists by their profile location area β neighborhood, borough, or district (`locations.area`). Multiple values create an OR filter.
+ updated_at_from (Optional[datetime]): Date. Filter for journalist profiles updated on or after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
+ updated_at_to (Optional[datetime]): Date. Filter for journalist profiles updated on or before this date. Accepts ISO 8601 format (e.g., 2023-03-01T23:59:59) or yyyy-mm-dd format.
+ show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
Returns:
- StorySearchResult: The response
+ JournalistSearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_STORIES
+ path = PATH_SEARCH_JOURNALISTS
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
+ if id is not None:
+ params["id"] = id
if q is not None:
params["q"] = q
if name is not None:
params["name"] = name
- if cluster_id is not None:
- params["clusterId"] = cluster_id
- if exclude_cluster_id is not None:
- params["excludeClusterId"] = exclude_cluster_id
+ if twitter is not None:
+ params["twitter"] = twitter
if sort_by is not None:
params["sortBy"] = sort_by
- if page is not None:
- params["page"] = page
if size is not None:
params["size"] = size
- if var_from is not None:
- params["from"] = var_from
- if to is not None:
- params["to"] = to
- if initialized_from is not None:
- params["initializedFrom"] = initialized_from
- if initialized_to is not None:
- params["initializedTo"] = initialized_to
- if updated_from is not None:
- params["updatedFrom"] = updated_from
- if updated_to is not None:
- params["updatedTo"] = updated_to
+ if page is not None:
+ params["page"] = page
+ if source is not None:
+ params["source"] = source
if topic is not None:
params["topic"] = topic
if category is not None:
params["category"] = category
- if taxonomy is not None:
- params["taxonomy"] = taxonomy
- if source is not None:
- params["source"] = source
- if source_group is not None:
- params["sourceGroup"] = source_group
- if min_unique_sources is not None:
- params["minUniqueSources"] = min_unique_sources
- if min_source_diversity is not None:
- params["minSourceDiversity"] = min_source_diversity
- if person_wikidata_id is not None:
- params["personWikidataId"] = person_wikidata_id
- if person_name is not None:
- params["personName"] = person_name
- if company_id is not None:
- params["companyId"] = company_id
- if company_name is not None:
- params["companyName"] = company_name
- if company_domain is not None:
- params["companyDomain"] = company_domain
- if company_symbol is not None:
- params["companySymbol"] = company_symbol
+ if label is not None:
+ params["label"] = label
+ if min_monthly_posts is not None:
+ params["minMonthlyPosts"] = min_monthly_posts
+ if max_monthly_posts is not None:
+ params["maxMonthlyPosts"] = max_monthly_posts
if country is not None:
params["country"] = country
- if state is not None:
- params["state"] = state
- if city is not None:
- params["city"] = city
- if area is not None:
- params["area"] = area
- if min_cluster_size is not None:
- params["minClusterSize"] = min_cluster_size
- if max_cluster_size is not None:
- params["maxClusterSize"] = max_cluster_size
- if name_exists is not None:
- params["nameExists"] = name_exists
- if positive_sentiment_from is not None:
- params["positiveSentimentFrom"] = positive_sentiment_from
- if positive_sentiment_to is not None:
- params["positiveSentimentTo"] = positive_sentiment_to
- if neutral_sentiment_from is not None:
- params["neutralSentimentFrom"] = neutral_sentiment_from
- if neutral_sentiment_to is not None:
- params["neutralSentimentTo"] = neutral_sentiment_to
- if negative_sentiment_from is not None:
- params["negativeSentimentFrom"] = negative_sentiment_from
- if negative_sentiment_to is not None:
- params["negativeSentimentTo"] = negative_sentiment_to
- if show_story_page_info is not None:
- params["showStoryPageInfo"] = show_story_page_info
+ if location_country is not None:
+ params["locationCountry"] = location_country
+ if location_state is not None:
+ params["locationState"] = location_state
+ if location_county is not None:
+ params["locationCounty"] = location_county
+ if location_city is not None:
+ params["locationCity"] = location_city
+ if location_area is not None:
+ params["locationArea"] = location_area
+ if updated_at_from is not None:
+ params["updatedAtFrom"] = updated_at_from
+ if updated_at_to is not None:
+ params["updatedAtTo"] = updated_at_to
if show_num_results is not None:
params["showNumResults"] = show_num_results
- if show_duplicates is not None:
- params["showDuplicates"] = show_duplicates
- if show_highlighting is not None:
- params["showHighlighting"] = show_highlighting
- if highlight_fragment_size is not None:
- params["highlightFragmentSize"] = highlight_fragment_size
- if highlight_num_fragments is not None:
- params["highlightNumFragments"] = highlight_num_fragments
- if highlight_pre_tag is not None:
- params["highlightPreTag"] = highlight_pre_tag
- if highlight_post_tag is not None:
- params["highlightPostTag"] = highlight_post_tag
- if highlight_q is not None:
- params["highlightQ"] = highlight_q
- if expand_articles is not None:
- params["expandArticles"] = expand_articles
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return StorySearchResult.model_validate(resp.json())
+ return JournalistSearchResult.model_validate(resp.json())
- # ----------------- search_stories (async) ----------------- #
- async def search_stories_async(
+ # ----------------- search_linkedin_posts (async) ----------------- #
+ async def search_linkedin_posts(
+ self,
+ q: str,
+ sort_by: Optional[str] = None,
+ date_posted: Optional[str] = None,
+ content_type: Optional[str] = None,
+ author_title: Optional[str] = None,
+ page: Optional[int] = None,
+ ) -> SocialLinkedInPostSearchResult:
+ """
+ Search public LinkedIn posts by keyword. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ q (str): Search keywords. (required)
+ sort_by (Optional[str]): Sort order of the results.
+ Allowed values: 'DATE_POSTED', 'RELEVANCE'
+ date_posted (Optional[str]): Only return posts published within this time window.
+ Allowed values: 'PAST_24H', 'PAST_WEEK', 'PAST_MONTH'
+ content_type (Optional[str]): Only return posts with this type of content.
+ Allowed values: 'PHOTOS', 'VIDEOS', 'JOBS', 'LIVE_VIDEOS', 'DOCUMENTS'
+ author_title (Optional[str]): Only return posts by authors whose title matches this value.
+ page (Optional[int]): Page number, starting at 1.
+
+ Returns:
+ SocialLinkedInPostSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_LINKEDIN_POSTS
+
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if date_posted is not None:
+ params["datePosted"] = date_posted
+ if content_type is not None:
+ params["contentType"] = content_type
+ if author_title is not None:
+ params["authorTitle"] = author_title
+ if page is not None:
+ params["page"] = page
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialLinkedInPostSearchResult.model_validate(resp.json())
+
+ # ----------------- search_people (async) ----------------- #
+ async def search_people(
self,
- q: Optional[str] = None,
name: Optional[str] = None,
- cluster_id: Optional[List[str]] = None,
- exclude_cluster_id: Optional[List[str]] = None,
- sort_by: Optional[SortBy] = None,
+ wikidata_id: Optional[List[str]] = None,
+ occupation_id: Optional[List[str]] = None,
+ occupation_label: Optional[str] = None,
page: Optional[int] = None,
size: Optional[int] = None,
- var_from: Optional[datetime] = None,
- to: Optional[datetime] = None,
- initialized_from: Optional[datetime] = None,
- initialized_to: Optional[datetime] = None,
- updated_from: Optional[datetime] = None,
- updated_to: Optional[datetime] = None,
- topic: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- taxonomy: Optional[List[str]] = None,
- source: Optional[List[str]] = None,
- source_group: Optional[List[str]] = None,
- min_unique_sources: Optional[int] = None,
- min_source_diversity: Optional[float] = None,
- person_wikidata_id: Optional[List[str]] = None,
- person_name: Optional[str] = None,
- company_id: Optional[List[str]] = None,
- company_name: Optional[str] = None,
- company_domain: Optional[List[str]] = None,
- company_symbol: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- state: Optional[List[str]] = None,
- city: Optional[List[str]] = None,
- area: Optional[List[str]] = None,
- min_cluster_size: Optional[int] = None,
- max_cluster_size: Optional[int] = None,
- name_exists: Optional[bool] = None,
- positive_sentiment_from: Optional[float] = None,
- positive_sentiment_to: Optional[float] = None,
- neutral_sentiment_from: Optional[float] = None,
- neutral_sentiment_to: Optional[float] = None,
- negative_sentiment_from: Optional[float] = None,
- negative_sentiment_to: Optional[float] = None,
- show_story_page_info: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- show_duplicates: Optional[bool] = None,
- show_highlighting: Optional[bool] = None,
- highlight_fragment_size: Optional[int] = None,
- highlight_num_fragments: Optional[int] = None,
- highlight_pre_tag: Optional[str] = None,
- highlight_post_tag: Optional[str] = None,
- highlight_q: Optional[str] = None,
- expand_articles: Optional[bool] = None,
- ) -> StorySearchResult:
+ ) -> PeopleSearchResult:
"""
- Async variant of search_stories. Track evolving narratives and top news headlines with the '**Stories**' endpoint β it returns structured clusters of related articles with summaries, key points, sentiment scores, and metadata on top people, companies, topics, countries, and categories. Use it to monitor how news stories develop over time, who and what they impact, and where they're gaining traction globally. Each story represents a cluster of related articles grouped during processing. Articles are assigned to a single story, identified by the `clusterId` field. Once a story reaches five articles, a story title is automatically generated based on its content. Stories update as new coverage is processed and key details evolve. To fetch all articles within a specific story, query the `/articles/all` endpoint using the `clusterId` parameter.
+ Search and retrieve additional information on known persons that exist within Perigon's entity database and as referenced in any article response object. Our database contains over 650,000 people from around the world and is refreshed frequently. People data is derived from Wikidata and includes a wikidataId field that can be used to lookup even more information on Wikidata's website.
Args:
- q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
- cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
- exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
- sort_by (Optional[SortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
+ name (Optional[str]): String. Search by person's name. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ wikidata_id (Optional[List[str]]): String Array. Filter by Wikidata entity IDs (e.g., Q7747, Q937). These are unique identifiers from Wikidata.org that precisely identify public figures and eliminate name ambiguity. Multiple values create an OR filter.
+ occupation_id (Optional[List[str]]): String Array. Filter by Wikidata occupation IDs (e.g., Q82955 for politician, Q33999 for actor, Q19546 for businessman). Finds people with specific professions. Multiple values create an OR filter.
+ occupation_label (Optional[str]): String. Search by occupation name (e.g., politician, actor, CEO, athlete). Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. 'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00
- to (Optional[datetime]): Date. 'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59
- initialized_from (Optional[datetime]): Date. Filter for stories created after this date. Alternative parameter for filtering by story creation date.
- initialized_to (Optional[datetime]): Date. Filter for stories created before this date. Alternative parameter for filtering by story creation date.
- updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
- updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
- topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
- category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
- taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
- source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
- source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
- min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
- min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
- person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
- person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
- company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
- company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
- company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
- company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
- country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
- state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
- min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
- max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
- name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
- positive_sentiment_from (Optional[float]): Float. Filter articles with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- positive_sentiment_to (Optional[float]): Float. Filter articles with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- neutral_sentiment_from (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- neutral_sentiment_to (Optional[float]): Float. Filter articles with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- negative_sentiment_from (Optional[float]): Float. Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- negative_sentiment_to (Optional[float]): Float. Filter articles with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- show_story_page_info (Optional[bool]): Boolean.
- show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
- show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
- show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
- highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
- highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
- highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
- highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
- highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
- expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
+ size (Optional[int]): Integer. The number of people to return per page in the paginated response.
Returns:
- StorySearchResult: The response
+ PeopleSearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_STORIES
+ path = PATH_SEARCH_PEOPLE
params: Dict[str, Any] = {}
- if q is not None:
- params["q"] = q
if name is not None:
params["name"] = name
- if cluster_id is not None:
- params["clusterId"] = cluster_id
- if exclude_cluster_id is not None:
- params["excludeClusterId"] = exclude_cluster_id
- if sort_by is not None:
- params["sortBy"] = sort_by
+ if wikidata_id is not None:
+ params["wikidataId"] = wikidata_id
+ if occupation_id is not None:
+ params["occupationId"] = occupation_id
+ if occupation_label is not None:
+ params["occupationLabel"] = occupation_label
if page is not None:
params["page"] = page
if size is not None:
params["size"] = size
- if var_from is not None:
- params["from"] = var_from
- if to is not None:
- params["to"] = to
- if initialized_from is not None:
- params["initializedFrom"] = initialized_from
- if initialized_to is not None:
- params["initializedTo"] = initialized_to
- if updated_from is not None:
- params["updatedFrom"] = updated_from
- if updated_to is not None:
- params["updatedTo"] = updated_to
- if topic is not None:
- params["topic"] = topic
- if category is not None:
- params["category"] = category
- if taxonomy is not None:
- params["taxonomy"] = taxonomy
- if source is not None:
- params["source"] = source
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return PeopleSearchResult.model_validate(resp.json())
+
+ # ----------------- search_reddit_posts (async) ----------------- #
+ async def search_reddit_posts(
+ self,
+ q: str,
+ subreddit: Optional[str] = None,
+ sort: Optional[str] = None,
+ time: Optional[str] = None,
+ cursor: Optional[str] = None,
+ ) -> SocialRedditPostSearchResult:
+ """
+ Search Reddit posts, optionally within a single subreddit. Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
+
+ Args:
+ q (str): Search query. (required)
+ subreddit (Optional[str]): Restrict the search to a subreddit (without the `r/` prefix).
+ sort (Optional[str]): Sort order of the results.
+ Allowed values: 'RELEVANCE', 'HOT', 'TOP', 'NEW', 'COMMENTS'
+ time (Optional[str]): Time window of the results. Only applies to the `TOP` sort.
+ Allowed values: 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR', 'ALL'
+ cursor (Optional[str]): `nextCursor` from the previous page.
+
+ Returns:
+ SocialRedditPostSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_REDDIT_POSTS
+
+ params: Dict[str, Any] = {}
+ if q is not None:
+ params["q"] = q
+ if subreddit is not None:
+ params["subreddit"] = subreddit
+ if sort is not None:
+ params["sort"] = sort
+ if time is not None:
+ params["time"] = time
+ if cursor is not None:
+ params["cursor"] = cursor
+ params = _normalise_query(params)
+
+ resp = await self.api_client.request("GET", path, params=params)
+ resp.raise_for_status()
+ return SocialRedditPostSearchResult.model_validate(resp.json())
+
+ # ----------------- search_sources (async) ----------------- #
+ async def search_sources(
+ self,
+ domain: Optional[List[str]] = None,
+ name: Optional[str] = None,
+ source_group: Optional[str] = None,
+ sort_by: Optional[SourcesSortBy] = None,
+ page: Optional[int] = None,
+ size: Optional[int] = None,
+ min_monthly_visits: Optional[int] = None,
+ max_monthly_visits: Optional[int] = None,
+ min_monthly_posts: Optional[int] = None,
+ max_monthly_posts: Optional[int] = None,
+ country: Optional[List[str]] = None,
+ source_country: Optional[List[str]] = None,
+ source_state: Optional[List[str]] = None,
+ source_county: Optional[List[str]] = None,
+ source_city: Optional[List[str]] = None,
+ source_lat: Optional[float] = None,
+ source_lon: Optional[float] = None,
+ source_max_distance: Optional[float] = None,
+ category: Optional[List[str]] = None,
+ topic: Optional[List[str]] = None,
+ label: Optional[List[str]] = None,
+ paywall: Optional[bool] = None,
+ show_subdomains: Optional[bool] = None,
+ show_num_results: Optional[bool] = None,
+ ) -> SourceSearchResult:
+ """
+ Search and filter the 200,000+ media sources available via the Perigon API. The result includes a list of individual media sources that were matched to your specific criteria.
+
+ Args:
+ domain (Optional[List[str]]): String Array. Filter by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com, us?.nytimes.com). Multiple values create an OR filter.
+ name (Optional[str]): String. Search by source name or alternative names. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ source_group (Optional[str]): String. Filter by predefined publisher bundles (e.g., top100, top50tech). Returns all sources within the specified group. See documentation for available source groups.
+ sort_by (Optional[SourcesSortBy]): String. Determines the source sorting order. Options include relevance (default, best match to query), globalRank (by overall traffic and popularity), monthlyVisits (by total monthly visitor count), and avgMonthlyPosts (by number of articles published monthly).
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of sources to return per page in the paginated response.
+ min_monthly_visits (Optional[int]): Integer. Filter for sources with at least this many monthly visitors. Used to target publishers by audience size.
+ max_monthly_visits (Optional[int]): Integer. Filter for sources with no more than this many monthly visitors. Used to target publishers by audience size.
+ min_monthly_posts (Optional[int]): Integer. Filter for sources that publish at least this many articles per month. Used to target publishers by content volume.
+ max_monthly_posts (Optional[int]): Integer. Filter for sources that publish no more than this many articles per month. Used to target publishers by content volume.
+ country (Optional[List[str]]): String Array. Filter sources by countries they commonly cover in their reporting. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
+ source_country (Optional[List[str]]): String Array. Filter for local publications based in specific countries. Uses ISO 3166-1 alpha-2 two-letter country codes in lowercase (e.g., us, gb, jp). See documentation for supported country codes. Multiple values create an OR filter.
+ source_state (Optional[List[str]]): String Array. Filter for local publications based in specific states or regions. Uses standard two-letter state codes in lowercase (e.g., ca, ny, tx). See documentation for supported state codes. Multiple values create an OR filter.
+ source_county (Optional[List[str]]): String Array. Filter for local publications based in specific counties. Multiple values create an OR filter.
+ source_city (Optional[List[str]]): String Array. Filter for local publications based in specific cities. Multiple values create an OR filter.
+ source_lat (Optional[float]): Float. Latitude coordinate for filtering local publications by geographic proximity. Used with sourceLon and sourceMaxDistance for radius search.
+ source_lon (Optional[float]): Float. Longitude coordinate for filtering local publications by geographic proximity. Used with sourceLat and sourceMaxDistance for radius search.
+ source_max_distance (Optional[float]): Float. Maximum distance in kilometers from the coordinates defined by sourceLat and sourceLon. Defines the radius for local publication searches.
+ category (Optional[List[str]]): String Array. Filter sources by their primary content categories such as Politics, Tech, Sports, Business, or Finance. Returns sources that frequently cover these topics. Multiple values create an OR filter.
+ topic (Optional[List[str]]): String Array. Filter sources by their frequently covered topics (e.g., Markets, Cryptocurrency, Climate Change). Returns sources where the specified topic is among their top 10 covered areas. Multiple values create an OR filter.
+ label (Optional[List[str]]): String Array. Filter sources by their content label patterns (e.g., Opinion, Paid-news, Non-news). Returns sources where the specified label is common in their published content. See documentation for all available labels. Multiple values create an OR filter.
+ paywall (Optional[bool]): Boolean. Filter by paywall status. Set to true to find sources with paywalls, or false to find sources without paywalls.
+ show_subdomains (Optional[bool]): Boolean. Controls whether subdomains are included as separate results. When true (default), subdomains appear as distinct sources. When false, results are consolidated to parent domains only.
+ show_num_results (Optional[bool]): Boolean. Controls whether to return the exact result count. When false (default), counts are capped at 10,000 for performance reasons. Set to true for precise counts in smaller result sets.
+
+ Returns:
+ SourceSearchResult: The response
+ """
+ # Get path template from class attribute
+ path = PATH_SEARCH_SOURCES
+
+ params: Dict[str, Any] = {}
+ if domain is not None:
+ params["domain"] = domain
+ if name is not None:
+ params["name"] = name
if source_group is not None:
params["sourceGroup"] = source_group
- if min_unique_sources is not None:
- params["minUniqueSources"] = min_unique_sources
- if min_source_diversity is not None:
- params["minSourceDiversity"] = min_source_diversity
- if person_wikidata_id is not None:
- params["personWikidataId"] = person_wikidata_id
- if person_name is not None:
- params["personName"] = person_name
- if company_id is not None:
- params["companyId"] = company_id
- if company_name is not None:
- params["companyName"] = company_name
- if company_domain is not None:
- params["companyDomain"] = company_domain
- if company_symbol is not None:
- params["companySymbol"] = company_symbol
+ if sort_by is not None:
+ params["sortBy"] = sort_by
+ if page is not None:
+ params["page"] = page
+ if size is not None:
+ params["size"] = size
+ if min_monthly_visits is not None:
+ params["minMonthlyVisits"] = min_monthly_visits
+ if max_monthly_visits is not None:
+ params["maxMonthlyVisits"] = max_monthly_visits
+ if min_monthly_posts is not None:
+ params["minMonthlyPosts"] = min_monthly_posts
+ if max_monthly_posts is not None:
+ params["maxMonthlyPosts"] = max_monthly_posts
if country is not None:
params["country"] = country
- if state is not None:
- params["state"] = state
- if city is not None:
- params["city"] = city
- if area is not None:
- params["area"] = area
- if min_cluster_size is not None:
- params["minClusterSize"] = min_cluster_size
- if max_cluster_size is not None:
- params["maxClusterSize"] = max_cluster_size
- if name_exists is not None:
- params["nameExists"] = name_exists
- if positive_sentiment_from is not None:
- params["positiveSentimentFrom"] = positive_sentiment_from
- if positive_sentiment_to is not None:
- params["positiveSentimentTo"] = positive_sentiment_to
- if neutral_sentiment_from is not None:
- params["neutralSentimentFrom"] = neutral_sentiment_from
- if neutral_sentiment_to is not None:
- params["neutralSentimentTo"] = neutral_sentiment_to
- if negative_sentiment_from is not None:
- params["negativeSentimentFrom"] = negative_sentiment_from
- if negative_sentiment_to is not None:
- params["negativeSentimentTo"] = negative_sentiment_to
- if show_story_page_info is not None:
- params["showStoryPageInfo"] = show_story_page_info
+ if source_country is not None:
+ params["sourceCountry"] = source_country
+ if source_state is not None:
+ params["sourceState"] = source_state
+ if source_county is not None:
+ params["sourceCounty"] = source_county
+ if source_city is not None:
+ params["sourceCity"] = source_city
+ if source_lat is not None:
+ params["sourceLat"] = source_lat
+ if source_lon is not None:
+ params["sourceLon"] = source_lon
+ if source_max_distance is not None:
+ params["sourceMaxDistance"] = source_max_distance
+ if category is not None:
+ params["category"] = category
+ if topic is not None:
+ params["topic"] = topic
+ if label is not None:
+ params["label"] = label
+ if paywall is not None:
+ params["paywall"] = paywall
+ if show_subdomains is not None:
+ params["showSubdomains"] = show_subdomains
if show_num_results is not None:
params["showNumResults"] = show_num_results
- if show_duplicates is not None:
- params["showDuplicates"] = show_duplicates
- if show_highlighting is not None:
- params["showHighlighting"] = show_highlighting
- if highlight_fragment_size is not None:
- params["highlightFragmentSize"] = highlight_fragment_size
- if highlight_num_fragments is not None:
- params["highlightNumFragments"] = highlight_num_fragments
- if highlight_pre_tag is not None:
- params["highlightPreTag"] = highlight_pre_tag
- if highlight_post_tag is not None:
- params["highlightPostTag"] = highlight_post_tag
- if highlight_q is not None:
- params["highlightQ"] = highlight_q
- if expand_articles is not None:
- params["expandArticles"] = expand_articles
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return StorySearchResult.model_validate(resp.json())
+ return SourceSearchResult.model_validate(resp.json())
- # ----------------- search_summarizer (sync) ----------------- #
- def search_summarizer(
+ # ----------------- search_stories (async) ----------------- #
+ async def search_stories(
self,
- summary_body: SummaryBody,
q: Optional[str] = None,
- title: Optional[str] = None,
- desc: Optional[str] = None,
- content: Optional[str] = None,
- summary: Optional[str] = None,
- url: Optional[str] = None,
- article_id: Optional[List[str]] = None,
+ name: Optional[str] = None,
cluster_id: Optional[List[str]] = None,
- sort_by: Optional[AllEndpointSortBy] = None,
+ exclude_cluster_id: Optional[List[str]] = None,
+ sort_by: Optional[ClustersListSortBy] = None,
page: Optional[int] = None,
size: Optional[int] = None,
var_from: Optional[datetime] = None,
to: Optional[datetime] = None,
- add_date_from: Optional[datetime] = None,
- add_date_to: Optional[datetime] = None,
- refresh_date_from: Optional[datetime] = None,
- refresh_date_to: Optional[datetime] = None,
- medium: Optional[List[str]] = None,
+ initialized_from: Optional[datetime] = None,
+ initialized_to: Optional[datetime] = None,
+ updated_from: Optional[datetime] = None,
+ updated_to: Optional[datetime] = None,
+ topic: Optional[List[str]] = None,
+ category: Optional[List[str]] = None,
+ taxonomy: Optional[List[str]] = None,
source: Optional[List[str]] = None,
source_group: Optional[List[str]] = None,
- exclude_source_group: Optional[List[str]] = None,
- exclude_source: Optional[List[str]] = None,
- watchlist: Optional[List[str]] = None,
- exclude_watchlist: Optional[List[str]] = None,
- paywall: Optional[bool] = None,
- byline: Optional[List[str]] = None,
- author: Optional[List[str]] = None,
- exclude_author: Optional[List[str]] = None,
- journalist_id: Optional[List[str]] = None,
- exclude_journalist_id: Optional[List[str]] = None,
- language: Optional[List[str]] = None,
- exclude_language: Optional[List[str]] = None,
- search_translation: Optional[bool] = None,
- label: Optional[List[str]] = None,
- exclude_label: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- exclude_category: Optional[List[str]] = None,
- topic: Optional[List[str]] = None,
- exclude_topic: Optional[List[str]] = None,
- link_to: Optional[str] = None,
- show_reprints: Optional[bool] = None,
- reprint_group_id: Optional[str] = None,
- city: Optional[List[str]] = None,
- exclude_city: Optional[List[str]] = None,
- area: Optional[List[str]] = None,
- state: Optional[List[str]] = None,
- exclude_state: Optional[List[str]] = None,
- county: Optional[List[str]] = None,
- exclude_county: Optional[List[str]] = None,
- locations_country: Optional[List[str]] = None,
- country: Optional[List[str]] = None,
- exclude_locations_country: Optional[List[str]] = None,
- location: Optional[List[str]] = None,
- lat: Optional[float] = None,
- lon: Optional[float] = None,
- max_distance: Optional[float] = None,
- source_city: Optional[List[str]] = None,
- exclude_source_city: Optional[List[str]] = None,
- source_county: Optional[List[str]] = None,
- exclude_source_county: Optional[List[str]] = None,
- source_country: Optional[List[str]] = None,
- exclude_source_country: Optional[List[str]] = None,
- source_state: Optional[List[str]] = None,
- exclude_source_state: Optional[List[str]] = None,
- source_lat: Optional[float] = None,
- source_lon: Optional[float] = None,
- source_max_distance: Optional[float] = None,
+ min_unique_sources: Optional[int] = None,
+ min_source_diversity: Optional[float] = None,
person_wikidata_id: Optional[List[str]] = None,
- exclude_person_wikidata_id: Optional[List[str]] = None,
- person_name: Optional[List[str]] = None,
- exclude_person_name: Optional[List[str]] = None,
+ person_name: Optional[str] = None,
company_id: Optional[List[str]] = None,
- exclude_company_id: Optional[List[str]] = None,
company_name: Optional[str] = None,
company_domain: Optional[List[str]] = None,
- exclude_company_domain: Optional[List[str]] = None,
company_symbol: Optional[List[str]] = None,
- exclude_company_symbol: Optional[List[str]] = None,
- show_num_results: Optional[bool] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ country: Optional[List[str]] = None,
+ state: Optional[List[str]] = None,
+ city: Optional[List[str]] = None,
+ area: Optional[List[str]] = None,
+ min_cluster_size: Optional[int] = None,
+ max_cluster_size: Optional[int] = None,
+ name_exists: Optional[bool] = None,
positive_sentiment_from: Optional[float] = None,
positive_sentiment_to: Optional[float] = None,
neutral_sentiment_from: Optional[float] = None,
neutral_sentiment_to: Optional[float] = None,
negative_sentiment_from: Optional[float] = None,
negative_sentiment_to: Optional[float] = None,
- taxonomy: Optional[List[str]] = None,
- prefix_taxonomy: Optional[str] = None,
+ show_story_page_info: Optional[bool] = None,
+ show_num_results: Optional[bool] = None,
+ show_duplicates: Optional[bool] = None,
show_highlighting: Optional[bool] = None,
highlight_fragment_size: Optional[int] = None,
highlight_num_fragments: Optional[int] = None,
highlight_pre_tag: Optional[str] = None,
highlight_post_tag: Optional[str] = None,
highlight_q: Optional[str] = None,
- ) -> SummarySearchResult:
+ expand_articles: Optional[bool] = None,
+ ) -> StorySearchResult:
"""
- Produce a single, concise summary over the full corpus of articles matching your filters, using your prompt to guide which insights to highlight.
-
- Args:
- summary_body (SummaryBody): Parameter summary_body (required)
- q (Optional[str]): String. Primary search query for filtering articles based on their title, description, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- title (Optional[str]): String. Search specifically within article headlines/titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- desc (Optional[str]): String. Search within article description fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
- content (Optional[str]): String. Search within the full article body content. Supports Boolean logic, exact phrase matching with quotes, and wildcards for comprehensive content searching.
- summary (Optional[str]): String. Search within article summary fields. Supports Boolean expressions, exact phrase matching with quotes, and wildcards for flexible pattern matching.
- url (Optional[str]): String. Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel.
- article_id (Optional[List[str]]): String Array. Retrieve specific news articles by their unique article identifiers. Multiple IDs can be provided to return a collection of specific articles.
- cluster_id (Optional[List[str]]): String Array. Filter results to only show content within a specific related content cluster. Returns articles grouped together as part of Perigon Stories based on topic relevance.
- sort_by (Optional[AllEndpointSortBy]): String. Determines the article sorting order. Options include relevance (default), date/pubDate (newest publication date first), reverseDate (oldest publication date first), addDate (newest ingestion date first), reverseAddDate (oldest ingestion date first), and refreshDate (most recently updated in system first, often identical to addDate).
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- var_from (Optional[datetime]): Date. Filter for articles published after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- to (Optional[datetime]): Date. Filter for articles published before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- add_date_from (Optional[datetime]): Date. Filter for articles added to Perigon's system after this date. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
- add_date_to (Optional[datetime]): Date. Filter for articles added to Perigon's system before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- refresh_date_from (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system after this date. In most cases yields similar results to addDateFrom but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
- refresh_date_to (Optional[datetime]): Date. Filter for articles refreshed/updated in Perigon's system before this date. In most cases yields similar results to addDateTo but can differ for updated content. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
- medium (Optional[List[str]]): String Array. Filter articles by their primary medium type. Accepts Article for written content or Video for video-based stories. Multiple values create an OR filter.
- source (Optional[List[str]]): String Array. Filter articles by specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an OR filter.
- source_group (Optional[List[str]]): String Array. Filter articles using Perigon's curated publisher bundles (e.g., top100, top25crypto). Multiple values create an OR filter to include articles from any of the specified bundles.
- exclude_source_group (Optional[List[str]]): String Array. Exclude articles from specified Perigon source groups. Multiple values create an AND-exclude filter, removing content from publishers in any of the specified bundles (e.g., top10, top100).
- exclude_source (Optional[List[str]]): String Array. Exclude articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). Multiple values create an AND-exclude filter.
- watchlist (Optional[List[str]]): String Array. Filter articles using watchlists of people and companies. Multiple values create an OR filter to include articles mentioning any entity from the specified watchlists.
- exclude_watchlist (Optional[List[str]]): String Array. Exclude articles mentioning entities from specified watchlists. Multiple values create an AND-exclude filter, removing content mentioning any entity from the specified watchlists.
- paywall (Optional[bool]): Boolean. Filter to show only results where the source has a paywall (true) or does not have a paywall (false).
- byline (Optional[List[str]]): String Array. Filter articles by author bylines. Works as an exact match for each author name provided. Multiple values create an OR filter to find articles by any of the specified authors.
- author (Optional[List[str]]): String Array. Filter articles by specific author names. Works as an exact match for each name. Multiple values create an OR filter to find articles by any of the specified authors.
- exclude_author (Optional[List[str]]): String Array. Exclude articles written by specific authors. Any article with an author name matching an entry in this list will be omitted from results. Multiple values create an AND-exclude filter.
- journalist_id (Optional[List[str]]): String Array. Filter by unique journalist identifiers which can be found through the Journalist API or in the matchedAuthors field. Multiple values create an OR filter.
- exclude_journalist_id (Optional[List[str]]): String Array. Exclude articles written by specific journalists identified by their unique IDs. Multiple values create an AND-exclude filter.
- language (Optional[List[str]]): String Array. Filter articles by their language using ISO-639 two-letter codes (e.g., en, es, fr). Multiple values create an OR filter.
- exclude_language (Optional[List[str]]): String Array. Exclude articles in specific languages using ISO-639 two-letter codes. Multiple values create an AND-exclude filter.
- search_translation (Optional[bool]): Boolean. Expand search to include translated content fields for non-English articles. When true, searches translated title, description, and content fields.
- label (Optional[List[str]]): String Array. Filter articles by editorial labels such as Opinion, Paid-news, Non-news, Fact Check, or Press Release. Multiple values create an OR filter.
- exclude_label (Optional[List[str]]): String Array. Exclude articles with specific editorial labels. Multiple values create an AND-exclude filter, removing all content with any of these labels.
- category (Optional[List[str]]): String Array. Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles.
- exclude_category (Optional[List[str]]): String Array. Exclude articles with specific categories. Multiple values create an AND-exclude filter, removing all content with any of these categories.
- topic (Optional[List[str]]): String Array. Filter by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and articles can have multiple topics. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
- exclude_topic (Optional[List[str]]): String Array. Exclude articles with specific topics. Multiple values create an AND-exclude filter, removing all content with any of these topics.
- link_to (Optional[str]): String. Returns only articles that contain links to the specified URL pattern. Matches against the 'links' field in article responses.
- show_reprints (Optional[bool]): Boolean. Controls whether to include reprinted content in results. When true (default), shows syndicated articles from wire services like AP or Reuters that appear on multiple sites.
- reprint_group_id (Optional[str]): String. Returns all articles in a specific reprint group, including the original article and all its known reprints. Use when you want to see all versions of the same content.
- city (Optional[List[str]]): String Array. Filters articles where a specified city plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the urban area in question. If multiple parameters are passed, they will be applied as OR operations.
- exclude_city (Optional[List[str]]): String Array. A list of cities to exclude from the results. Articles that are associated with any of the specified cities will be filtered out.
- area (Optional[List[str]]): String Array. Filters articles where a specified area, such as a neighborhood, borough, or district, plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the area in question. If multiple parameters are passed, they will be applied as OR operations.
- state (Optional[List[str]]): String Array. Filters articles where a specified state plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the state in question. If multiple parameters are passed, they will be applied as OR operations.
- exclude_state (Optional[List[str]]): String Array. A list of states to exclude. Articles that include, or are associated with, any of the states provided here will be filtered out. This is especially useful if you want to ignore news tied to certain geographical areas (e.g., US states).
- county (Optional[List[str]]): String Array. A list of counties to include (or specify) in the search results. This field filters the returned articles based on the county associated with the event or news. Only articles tagged with one of these counties will be included.
- exclude_county (Optional[List[str]]): String Array. Excludes articles from specific counties or administrative divisions in the search results. Accepts either a single county name or a list of county names. County names should match the format used in article metadata (e.g., 'Los Angeles County', 'Cook County'). This parameter allows for more granular geographic filter
- locations_country (Optional[List[str]]): String Array. Filters articles where a specified country plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the country in question. If multiple parameters are passed, they will be applied as OR operations.
+ Track evolving narratives and top news headlines with the '**Stories**' endpoint β it returns structured clusters of related articles with summaries, key points, sentiment scores, and metadata on top people, companies, topics, countries, and categories. Use it to monitor how news stories develop over time, who and what they impact, and where they're gaining traction globally. Each story represents a cluster of related articles grouped during processing. Articles are assigned to a single story, identified by the `clusterId` field. Once a story reaches five articles, a story title is automatically generated based on its content. Stories update as new coverage is processed and key details evolve. To fetch all articles within a specific story, query the `/articles/all` endpoint using the `clusterId` parameter.
+
+ Args:
+ q (Optional[str]): String. Primary search query for filtering stories based on their name, summary, and key points. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
+ name (Optional[str]): String. Search specifically within story names. Supports Boolean operators, exact phrases with quotes, and wildcards for matching name variations.
+ cluster_id (Optional[List[str]]): String Array. Filter to specific stories using their unique identifiers. Each clusterId represents a distinct story that groups related articles. Multiple values create an OR filter.
+ exclude_cluster_id (Optional[List[str]]): String Array. Excludes specific stories from the results by their unique identifiers. Use this parameter to filter out unwanted or previously seen stories.
+ sort_by (Optional[ClustersListSortBy]): String. Determines the story sorting order. Options include createdAt (default, when stories first emerged), updatedAt (when stories received new articles, best for tracking developing events), relevance (best match to query), count (by unique article count), and totalCount (by total article count including reprints).
+ page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
+ size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
+ var_from (Optional[datetime]): Filter for stories created on or after this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ to (Optional[datetime]): Filter for stories created on or before this date. Accepts ISO 8601 or yyyy-mm-dd format.
+ initialized_from (Optional[datetime]): Filter for stories initialized on or after this date. A story is initialized when it becomes visible with generated story data.
+ initialized_to (Optional[datetime]): Filter for stories initialized on or before this date. A story is initialized when it becomes visible with generated story data.
+ updated_from (Optional[datetime]): Date. Filter for stories that received new articles after this date. Useful for tracking developing news events or evolving storylines.
+ updated_to (Optional[datetime]): Date. Filter for stories that received new articles before this date. Useful for tracking developing news events or evolving storylines.
+ topic (Optional[List[str]]): String Array. Filter stories by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Topics are more granular than categories, and stories can include multiple topics based on their constituent articles. Use the /topics endpoint for a complete list of available topics. Multiple values create an OR filter.
+ category (Optional[List[str]]): String Array. Filter stories by broad content categories such as Politics, Tech, Sports, Business, or Finance. Use 'none' to find uncategorized stories. Categories are derived from the articles within each story. Multiple values create an OR filter.
+ taxonomy (Optional[List[str]]): String Array. Filter stories by Google Content Categories. Must pass the full hierarchical path of the category. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Stories are categorized based on their constituent articles. Multiple values create an OR filter.
+ source (Optional[List[str]]): String Array. Filter stories that contain articles from specific publisher domains or subdomains. Supports wildcards (* and ?) for pattern matching (e.g., *.cnn.com). A story will match if it contains at least one article from any of the specified sources. Multiple values create an OR filter.
+ source_group (Optional[List[str]]): String Array. Filter stories that contain articles from publishers in Perigon's curated bundles (e.g., top100, top25crypto). A story will match if it contains at least one article from any publisher in the specified bundles. Multiple values create an OR filter.
+ min_unique_sources (Optional[int]): Integer. Specifies the minimum number of unique sources required for a story to appear in results. Higher values return more significant stories covered by multiple publications. Default is 3.
+ min_source_diversity (Optional[float]): Float. Minimum ratio of unique sources to unique articles (uniqueSources / uniqueCount). Filters out stories dominated by a single publisher. For example, a value of 0.05 requires at least 1 unique source per 20 articles. Not applied by default.
+ person_wikidata_id (Optional[List[str]]): String Array. Filter stories by Wikidata IDs of top mentioned people. Returns stories where these individuals appear prominently. Refer to the /people endpoint for a complete list of tracked individuals.
+ person_name (Optional[str]): String. Filter stories by exact name matches of top mentioned people. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
+ company_id (Optional[List[str]]): String Array. Filter stories by identifiers of top mentioned companies. Returns stories where these companies appear prominently. For a complete list of tracked companies, refer to the /companies endpoint.
+ company_name (Optional[str]): String. Filter stories by names of top mentioned companies. Performs an exact match on company names in the topCompanies field.
+ company_domain (Optional[List[str]]): String Array. Filter stories by domains of top mentioned companies (e.g., apple.com). Returns stories where companies with these domains appear prominently. For available company entities, consult the /companies endpoint.
+ company_symbol (Optional[List[str]]): String Array. Filter stories by stock symbols of top mentioned companies. Returns stories where companies with these symbols appear prominently. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes of top mentioned companies (International Securities Identification Numbers). Returns stories where companies with these ISINs appear prominently. For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter stories by ISIN codes on ticker listings of top mentioned companies (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. Returns stories where those companies appear prominently. For available company entities and their listing ISINs, consult the /companies endpoint.
country (Optional[List[str]]): String Array. Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.
- exclude_locations_country (Optional[List[str]]): String Array. Excludes articles where a specified country plays a central role in the content, ensuring results are not deeply relevant to the country in question. If multiple parameters are passed, they will be applied as AND operations, excluding articles relevant to any of the specified countries.
- location (Optional[List[str]]): String Array. Return all articles that have the specified location. Location attributes are delimited by ':' between key and value, and '::' between attributes. Example: 'city:New York::state:NY'.
- lat (Optional[float]): Float. Latitude of the center point to search places
- lon (Optional[float]): Float. Longitude of the center point to search places
- max_distance (Optional[float]): Float. Maximum distance (in km) from starting point to search articles by tagged places
- source_city (Optional[List[str]]): String Array. Find articles published by sources that are located within a given city.
- exclude_source_city (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified cities.
- source_county (Optional[List[str]]): String Array. Find articles published by sources that are located within a given county.
- exclude_source_county (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified counties.
- source_country (Optional[List[str]]): String Array. Find articles published by sources that are located within a given country. Must be 2 character country code (i.e. us, gb, etc).
- exclude_source_country (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified countries. Must be 2 character country codes (e.g., us, gb).
- source_state (Optional[List[str]]): String Array. Find articles published by sources that are located within a given state.
- exclude_source_state (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified states.
- source_lat (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
- source_lon (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
- source_max_distance (Optional[float]): Float. Maximum distance from starting point to search articles created by local publications.
- person_wikidata_id (Optional[List[str]]): String Array. Filter articles by Wikidata IDs of mentioned people. Refer to the /people endpoint for a complete list of tracked individuals.
- exclude_person_wikidata_id (Optional[List[str]]): String Array. Exclude articles mentioning people with specific Wikidata IDs. Creates an AND-exclude filter to remove content about these individuals. Uses precise identifiers to avoid name ambiguity.
- person_name (Optional[List[str]]): String Array. Filter articles by exact person name matches. Does not support Boolean or complex logic. For available person entities, consult the /people endpoint.
- exclude_person_name (Optional[List[str]]): String Array. Exclude articles mentioning specific people by name. Creates an AND-exclude filter to remove content about these individuals.
- company_id (Optional[List[str]]): String Array. Filter articles by company identifiers. For a complete list of tracked companies, refer to the /companies endpoint.
- exclude_company_id (Optional[List[str]]): String Array. Exclude articles mentioning companies with specific identifiers. Creates an AND-exclude filter to remove content about these corporate entities.
- company_name (Optional[str]): String. Filter articles by company name mentions. Performs an exact match on company names.
- company_domain (Optional[List[str]]): String Array. Filter articles by company domains (e.g., apple.com). For available company entities, consult the /companies endpoint.
- exclude_company_domain (Optional[List[str]]): String Array. Exclude articles related to companies with specific domains. Creates an AND-exclude filter to remove content about these companies.
- company_symbol (Optional[List[str]]): String Array. Filter articles by company stock symbols. For available company entities and their symbols, consult the /companies endpoint.
- exclude_company_symbol (Optional[List[str]]): String Array. A list of stock symbols (ticker symbols) that identify companies to be excluded. Articles related to companies using any of these symbols will be omitted, which is useful for targeting or avoiding specific public companies.
- show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.
- positive_sentiment_from (Optional[float]): Float. Filter articles with a positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- positive_sentiment_to (Optional[float]): Float. Filter articles with a positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
- neutral_sentiment_from (Optional[float]): Float. Filter articles with a neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- neutral_sentiment_to (Optional[float]): Float. Filter articles with a neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
- negative_sentiment_from (Optional[float]): Float. Filter articles with a negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- negative_sentiment_to (Optional[float]): Float. Filter articles with a negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
- taxonomy (Optional[List[str]]): String Array. Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds. [Full list](https://docs.cloud.google.com/natural-language/docs/categories#version_2)
- prefix_taxonomy (Optional[str]): String. Filters by Google Content Categories. This field will filter by the category prefix only. Example: prefixTaxonomy=/Finance
+ state (Optional[List[str]]): String Array. Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ city (Optional[List[str]]): String Array. Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ area (Optional[List[str]]): String Array. Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.
+ min_cluster_size (Optional[int]): Integer. Filter by minimum cluster size. Minimum cluster size filter applies to number of unique articles.
+ max_cluster_size (Optional[int]): Integer. Filter by maximum cluster size. Maximum cluster size filter applies to number of unique articles in the cluster.
+ name_exists (Optional[bool]): Boolean. Filter to only include stories that have been assigned names. Defaults to true. Note that stories only receive names after they contain at least 5 unique articles.
+ positive_sentiment_from (Optional[float]): Filter stories with an aggregate positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ positive_sentiment_to (Optional[float]): Filter stories with an aggregate positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
+ neutral_sentiment_from (Optional[float]): Filter stories with an aggregate neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ neutral_sentiment_to (Optional[float]): Filter stories with an aggregate neutral sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
+ negative_sentiment_from (Optional[float]): Filter stories with an aggregate negative sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ negative_sentiment_to (Optional[float]): Filter stories with an aggregate negative sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger negative tone.
+ show_story_page_info (Optional[bool]): Boolean.
+ show_num_results (Optional[bool]): Boolean. Show total number of results. By default set to false, will cap result count at 10000.
+ show_duplicates (Optional[bool]): Boolean. Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. duplicateOf field contains the original cluster Id. When showDuplicates=true, all stories are shown.
show_highlighting (Optional[bool]): Boolean. When set to true, enables text highlighting in search results.
highlight_fragment_size (Optional[int]): Integer. Specifies the size in characters of each highlighted text fragment. Defaults to 100 if not specified.
highlight_num_fragments (Optional[int]): Integer. Controls the maximum number of highlighted fragments to return per field.
highlight_pre_tag (Optional[str]): String. Defines the HTML tag that appears before highlighted text. Defaults to '' if not specified.
highlight_post_tag (Optional[str]): String. Defines the HTML tag that appears after highlighted text. Defaults to '' if not specified.
highlight_q (Optional[str]): String. Specifies a separate query for highlighting, allowing highlights based on terms different from the main search query. Example: main query 'q=climate change' with 'highlightQ=renewable OR solar' will highlight terms 'renewable' and 'solar' in results about climate change.
+ expand_articles (Optional[bool]): Boolean. Preview 5 articles from the cluster.
Returns:
- SummarySearchResult: The response
+ StorySearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_SUMMARIZER
+ path = PATH_SEARCH_STORIES
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
if q is not None:
params["q"] = q
- if title is not None:
- params["title"] = title
- if desc is not None:
- params["desc"] = desc
- if content is not None:
- params["content"] = content
- if summary is not None:
- params["summary"] = summary
- if url is not None:
- params["url"] = url
- if article_id is not None:
- params["articleId"] = article_id
+ if name is not None:
+ params["name"] = name
if cluster_id is not None:
params["clusterId"] = cluster_id
+ if exclude_cluster_id is not None:
+ params["excludeClusterId"] = exclude_cluster_id
if sort_by is not None:
params["sortBy"] = sort_by
if page is not None:
@@ -3378,138 +5443,58 @@ def search_summarizer(
params["from"] = var_from
if to is not None:
params["to"] = to
- if add_date_from is not None:
- params["addDateFrom"] = add_date_from
- if add_date_to is not None:
- params["addDateTo"] = add_date_to
- if refresh_date_from is not None:
- params["refreshDateFrom"] = refresh_date_from
- if refresh_date_to is not None:
- params["refreshDateTo"] = refresh_date_to
- if medium is not None:
- params["medium"] = medium
+ if initialized_from is not None:
+ params["initializedFrom"] = initialized_from
+ if initialized_to is not None:
+ params["initializedTo"] = initialized_to
+ if updated_from is not None:
+ params["updatedFrom"] = updated_from
+ if updated_to is not None:
+ params["updatedTo"] = updated_to
+ if topic is not None:
+ params["topic"] = topic
+ if category is not None:
+ params["category"] = category
+ if taxonomy is not None:
+ params["taxonomy"] = taxonomy
if source is not None:
params["source"] = source
if source_group is not None:
params["sourceGroup"] = source_group
- if exclude_source_group is not None:
- params["excludeSourceGroup"] = exclude_source_group
- if exclude_source is not None:
- params["excludeSource"] = exclude_source
- if watchlist is not None:
- params["watchlist"] = watchlist
- if exclude_watchlist is not None:
- params["excludeWatchlist"] = exclude_watchlist
- if paywall is not None:
- params["paywall"] = paywall
- if byline is not None:
- params["byline"] = byline
- if author is not None:
- params["author"] = author
- if exclude_author is not None:
- params["excludeAuthor"] = exclude_author
- if journalist_id is not None:
- params["journalistId"] = journalist_id
- if exclude_journalist_id is not None:
- params["excludeJournalistId"] = exclude_journalist_id
- if language is not None:
- params["language"] = language
- if exclude_language is not None:
- params["excludeLanguage"] = exclude_language
- if search_translation is not None:
- params["searchTranslation"] = search_translation
- if label is not None:
- params["label"] = label
- if exclude_label is not None:
- params["excludeLabel"] = exclude_label
- if category is not None:
- params["category"] = category
- if exclude_category is not None:
- params["excludeCategory"] = exclude_category
- if topic is not None:
- params["topic"] = topic
- if exclude_topic is not None:
- params["excludeTopic"] = exclude_topic
- if link_to is not None:
- params["linkTo"] = link_to
- if show_reprints is not None:
- params["showReprints"] = show_reprints
- if reprint_group_id is not None:
- params["reprintGroupId"] = reprint_group_id
- if city is not None:
- params["city"] = city
- if exclude_city is not None:
- params["excludeCity"] = exclude_city
- if area is not None:
- params["area"] = area
- if state is not None:
- params["state"] = state
- if exclude_state is not None:
- params["excludeState"] = exclude_state
- if county is not None:
- params["county"] = county
- if exclude_county is not None:
- params["excludeCounty"] = exclude_county
- if locations_country is not None:
- params["locationsCountry"] = locations_country
- if country is not None:
- params["country"] = country
- if exclude_locations_country is not None:
- params["excludeLocationsCountry"] = exclude_locations_country
- if location is not None:
- params["location"] = location
- if lat is not None:
- params["lat"] = lat
- if lon is not None:
- params["lon"] = lon
- if max_distance is not None:
- params["maxDistance"] = max_distance
- if source_city is not None:
- params["sourceCity"] = source_city
- if exclude_source_city is not None:
- params["excludeSourceCity"] = exclude_source_city
- if source_county is not None:
- params["sourceCounty"] = source_county
- if exclude_source_county is not None:
- params["excludeSourceCounty"] = exclude_source_county
- if source_country is not None:
- params["sourceCountry"] = source_country
- if exclude_source_country is not None:
- params["excludeSourceCountry"] = exclude_source_country
- if source_state is not None:
- params["sourceState"] = source_state
- if exclude_source_state is not None:
- params["excludeSourceState"] = exclude_source_state
- if source_lat is not None:
- params["sourceLat"] = source_lat
- if source_lon is not None:
- params["sourceLon"] = source_lon
- if source_max_distance is not None:
- params["sourceMaxDistance"] = source_max_distance
+ if min_unique_sources is not None:
+ params["minUniqueSources"] = min_unique_sources
+ if min_source_diversity is not None:
+ params["minSourceDiversity"] = min_source_diversity
if person_wikidata_id is not None:
params["personWikidataId"] = person_wikidata_id
- if exclude_person_wikidata_id is not None:
- params["excludePersonWikidataId"] = exclude_person_wikidata_id
if person_name is not None:
params["personName"] = person_name
- if exclude_person_name is not None:
- params["excludePersonName"] = exclude_person_name
if company_id is not None:
params["companyId"] = company_id
- if exclude_company_id is not None:
- params["excludeCompanyId"] = exclude_company_id
if company_name is not None:
params["companyName"] = company_name
if company_domain is not None:
params["companyDomain"] = company_domain
- if exclude_company_domain is not None:
- params["excludeCompanyDomain"] = exclude_company_domain
if company_symbol is not None:
params["companySymbol"] = company_symbol
- if exclude_company_symbol is not None:
- params["excludeCompanySymbol"] = exclude_company_symbol
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if country is not None:
+ params["country"] = country
+ if state is not None:
+ params["state"] = state
+ if city is not None:
+ params["city"] = city
+ if area is not None:
+ params["area"] = area
+ if min_cluster_size is not None:
+ params["minClusterSize"] = min_cluster_size
+ if max_cluster_size is not None:
+ params["maxClusterSize"] = max_cluster_size
+ if name_exists is not None:
+ params["nameExists"] = name_exists
if positive_sentiment_from is not None:
params["positiveSentimentFrom"] = positive_sentiment_from
if positive_sentiment_to is not None:
@@ -3522,10 +5507,12 @@ def search_summarizer(
params["negativeSentimentFrom"] = negative_sentiment_from
if negative_sentiment_to is not None:
params["negativeSentimentTo"] = negative_sentiment_to
- if taxonomy is not None:
- params["taxonomy"] = taxonomy
- if prefix_taxonomy is not None:
- params["prefixTaxonomy"] = prefix_taxonomy
+ if show_story_page_info is not None:
+ params["showStoryPageInfo"] = show_story_page_info
+ if show_num_results is not None:
+ params["showNumResults"] = show_num_results
+ if show_duplicates is not None:
+ params["showDuplicates"] = show_duplicates
if show_highlighting is not None:
params["showHighlighting"] = show_highlighting
if highlight_fragment_size is not None:
@@ -3538,19 +5525,16 @@ def search_summarizer(
params["highlightPostTag"] = highlight_post_tag
if highlight_q is not None:
params["highlightQ"] = highlight_q
+ if expand_articles is not None:
+ params["expandArticles"] = expand_articles
params = _normalise_query(params)
- resp = self.api_client.request(
- "POST",
- path,
- params=params,
- json=summary_body.model_dump(by_alias=True, exclude_none=True),
- )
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return SummarySearchResult.model_validate(resp.json())
+ return StorySearchResult.model_validate(resp.json())
# ----------------- search_summarizer (async) ----------------- #
- async def search_summarizer_async(
+ async def search_summarizer(
self,
summary_body: SummaryBody,
q: Optional[str] = None,
@@ -3563,7 +5547,6 @@ async def search_summarizer_async(
cluster_id: Optional[List[str]] = None,
sort_by: Optional[AllEndpointSortBy] = None,
page: Optional[int] = None,
- size: Optional[int] = None,
var_from: Optional[datetime] = None,
to: Optional[datetime] = None,
add_date_from: Optional[datetime] = None,
@@ -3630,8 +5613,11 @@ async def search_summarizer_async(
company_domain: Optional[List[str]] = None,
exclude_company_domain: Optional[List[str]] = None,
company_symbol: Optional[List[str]] = None,
+ company_isin: Optional[List[str]] = None,
+ company_symbol_isin: Optional[List[str]] = None,
+ exclude_company_isin: Optional[List[str]] = None,
+ exclude_company_symbol_isin: Optional[List[str]] = None,
exclude_company_symbol: Optional[List[str]] = None,
- show_num_results: Optional[bool] = None,
positive_sentiment_from: Optional[float] = None,
positive_sentiment_to: Optional[float] = None,
neutral_sentiment_from: Optional[float] = None,
@@ -3648,7 +5634,7 @@ async def search_summarizer_async(
highlight_q: Optional[str] = None,
) -> SummarySearchResult:
"""
- Async variant of search_summarizer. Produce a single, concise summary over the full corpus of articles matching your filters, using your prompt to guide which insights to highlight.
+ Produce one concise summary from up to 100 articles matching the supplied filters. Use the prompt and summarization fields to control which insights are highlighted. When method is CLUSTERS, article-only parameters such as articleId, page, sortBy, watchlist, reprintGroupId, exclusion filters, prefixTaxonomy, and highlighting options are ignored.
Args:
summary_body (SummaryBody): Parameter summary_body (required)
@@ -3662,7 +5648,6 @@ async def search_summarizer_async(
cluster_id (Optional[List[str]]): String Array. Filter results to only show content within a specific related content cluster. Returns articles grouped together as part of Perigon Stories based on topic relevance.
sort_by (Optional[AllEndpointSortBy]): String. Determines the article sorting order. Options include relevance (default), date/pubDate (newest publication date first), reverseDate (oldest publication date first), addDate (newest ingestion date first), reverseAddDate (oldest ingestion date first), and refreshDate (most recently updated in system first, often identical to addDate).
page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
var_from (Optional[datetime]): Date. Filter for articles published after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
to (Optional[datetime]): Date. Filter for articles published before this date. Accepts ISO 8601 format (e.g., 2022-02-01T23:59:59) or yyyy-mm-dd format.
add_date_from (Optional[datetime]): Date. Filter for articles added to Perigon's system after this date. Accepts ISO 8601 format (e.g., 2022-02-01T00:00:00) or yyyy-mm-dd format.
@@ -3717,7 +5702,7 @@ async def search_summarizer_async(
source_state (Optional[List[str]]): String Array. Find articles published by sources that are located within a given state.
exclude_source_state (Optional[List[str]]): String Array. Excludes articles published by sources that are located within the specified states.
source_lat (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
- source_lon (Optional[float]): Float. Latitude of the center point to search articles created by local publications.
+ source_lon (Optional[float]): Float. Longitude of the center point to search articles created by local publications.
source_max_distance (Optional[float]): Float. Maximum distance from starting point to search articles created by local publications.
person_wikidata_id (Optional[List[str]]): String Array. Filter articles by Wikidata IDs of mentioned people. Refer to the /people endpoint for a complete list of tracked individuals.
exclude_person_wikidata_id (Optional[List[str]]): String Array. Exclude articles mentioning people with specific Wikidata IDs. Creates an AND-exclude filter to remove content about these individuals. Uses precise identifiers to avoid name ambiguity.
@@ -3729,8 +5714,11 @@ async def search_summarizer_async(
company_domain (Optional[List[str]]): String Array. Filter articles by company domains (e.g., apple.com). For available company entities, consult the /companies endpoint.
exclude_company_domain (Optional[List[str]]): String Array. Exclude articles related to companies with specific domains. Creates an AND-exclude filter to remove content about these companies.
company_symbol (Optional[List[str]]): String Array. Filter articles by company stock symbols. For available company entities and their symbols, consult the /companies endpoint.
+ company_isin (Optional[List[str]]): String Array. Filter articles by company ISIN codes (International Securities Identification Numbers). For available company entities and their ISINs, consult the /companies endpoint.
+ company_symbol_isin (Optional[List[str]]): String Array. Filter articles by ISIN codes on company ticker listings (symbols.isin). Distinct from companyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
+ exclude_company_isin (Optional[List[str]]): String Array. Exclude articles related to companies with specific ISIN codes. Creates an AND-exclude filter to remove content about these companies. For available company entities and their ISINs, consult the /companies endpoint.
+ exclude_company_symbol_isin (Optional[List[str]]): String Array. Exclude articles related to companies whose ticker listings have these ISIN codes (symbols.isin). Distinct from excludeCompanyIsin, which matches the company-level ISIN. For available company entities and their listing ISINs, consult the /companies endpoint.
exclude_company_symbol (Optional[List[str]]): String Array. A list of stock symbols (ticker symbols) that identify companies to be excluded. Articles related to companies using any of these symbols will be omitted, which is useful for targeting or avoiding specific public companies.
- show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.
positive_sentiment_from (Optional[float]): Float. Filter articles with a positive sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
positive_sentiment_to (Optional[float]): Float. Filter articles with a positive sentiment score less than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger positive tone.
neutral_sentiment_from (Optional[float]): Float. Filter articles with a neutral sentiment score greater than or equal to the specified value. Scores range from 0 to 1, with higher values indicating stronger neutral tone.
@@ -3773,8 +5761,6 @@ async def search_summarizer_async(
params["sortBy"] = sort_by
if page is not None:
params["page"] = page
- if size is not None:
- params["size"] = size
if var_from is not None:
params["from"] = var_from
if to is not None:
@@ -3907,10 +5893,16 @@ async def search_summarizer_async(
params["excludeCompanyDomain"] = exclude_company_domain
if company_symbol is not None:
params["companySymbol"] = company_symbol
+ if company_isin is not None:
+ params["companyIsin"] = company_isin
+ if company_symbol_isin is not None:
+ params["companySymbolIsin"] = company_symbol_isin
+ if exclude_company_isin is not None:
+ params["excludeCompanyIsin"] = exclude_company_isin
+ if exclude_company_symbol_isin is not None:
+ params["excludeCompanySymbolIsin"] = exclude_company_symbol_isin
if exclude_company_symbol is not None:
params["excludeCompanySymbol"] = exclude_company_symbol
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
if positive_sentiment_from is not None:
params["positiveSentimentFrom"] = positive_sentiment_from
if positive_sentiment_to is not None:
@@ -3941,60 +5933,17 @@ async def search_summarizer_async(
params["highlightQ"] = highlight_q
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"POST",
path,
params=params,
- json=summary_body.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(summary_body),
)
resp.raise_for_status()
return SummarySearchResult.model_validate(resp.json())
- # ----------------- search_topics (sync) ----------------- #
- def search_topics(
- self,
- name: Optional[str] = None,
- category: Optional[str] = None,
- subcategory: Optional[str] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- ) -> TopicSearchResult:
- """
- Search through all available Topics that exist within the Perigon Database.
-
- Args:
- name (Optional[str]): String. Search for topics by exact name or partial text match. Does not support wildcards. Examples include Markets, Cryptocurrency, Climate Change, etc.
- category (Optional[str]): String. Filter topics by broad article categories such as Politics, Tech, Sports, Business, Finance, Entertainment, etc.
- subcategory (Optional[str]): String. Filter topics by their specific subcategory. Subcategories provide more granular classification beyond the main category, such as TV or Event.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of topics to return per page in the paginated response.
-
- Returns:
- TopicSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_SEARCH_TOPICS
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- if name is not None:
- params["name"] = name
- if category is not None:
- params["category"] = category
- if subcategory is not None:
- params["subcategory"] = subcategory
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- params = _normalise_query(params)
-
- resp = self.api_client.request("GET", path, params=params)
- resp.raise_for_status()
- return TopicSearchResult.model_validate(resp.json())
-
# ----------------- search_topics (async) ----------------- #
- async def search_topics_async(
+ async def search_topics(
self,
name: Optional[str] = None,
category: Optional[str] = None,
@@ -4003,7 +5952,7 @@ async def search_topics_async(
size: Optional[int] = None,
) -> TopicSearchResult:
"""
- Async variant of search_topics. Search through all available Topics that exist within the Perigon Database.
+ Search through all available Topics that exist within the Perigon Database.
Args:
name (Optional[str]): String. Search for topics by exact name or partial text match. Does not support wildcards. Examples include Markets, Cryptocurrency, Climate Change, etc.
@@ -4031,12 +5980,12 @@ async def search_topics_async(
params["size"] = size
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
return TopicSearchResult.model_validate(resp.json())
- # ----------------- search_wikipedia (sync) ----------------- #
- def search_wikipedia(
+ # ----------------- search_wikipedia (async) ----------------- #
+ async def search_wikipedia(
self,
q: Optional[str] = None,
title: Optional[str] = None,
@@ -4103,7 +6052,6 @@ def search_wikipedia(
# Get path template from class attribute
path = PATH_SEARCH_WIKIPEDIA
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
if q is not None:
params["q"] = q
@@ -4159,238 +6107,151 @@ def search_wikipedia(
params["sortBy"] = sort_by
params = _normalise_query(params)
- resp = self.api_client.request("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
return WikipediaSearchResult.model_validate(resp.json())
- # ----------------- search_wikipedia (async) ----------------- #
- async def search_wikipedia_async(
- self,
- q: Optional[str] = None,
- title: Optional[str] = None,
- summary: Optional[str] = None,
- text: Optional[str] = None,
- reference: Optional[str] = None,
- id: Optional[List[str]] = None,
- wiki_page_id: Optional[List[int]] = None,
- wiki_revision_id: Optional[List[int]] = None,
- wiki_code: Optional[List[str]] = None,
- wiki_namespace: Optional[List[int]] = None,
- wikidata_id: Optional[List[str]] = None,
- wikidata_instance_of_id: Optional[List[str]] = None,
- wikidata_instance_of_label: Optional[List[str]] = None,
- category: Optional[List[str]] = None,
- section_id: Optional[List[str]] = None,
- wiki_revision_from: Optional[datetime] = None,
- wiki_revision_to: Optional[datetime] = None,
- scraped_at_from: Optional[datetime] = None,
- scraped_at_to: Optional[datetime] = None,
- pageviews_from: Optional[int] = None,
- pageviews_to: Optional[int] = None,
- with_pageviews: Optional[bool] = None,
- show_num_results: Optional[bool] = None,
- page: Optional[int] = None,
- size: Optional[int] = None,
- sort_by: Optional[str] = None,
- ) -> WikipediaSearchResult:
+ # ----------------- search_x_posts (async) ----------------- #
+ async def search_x_posts(
+ self, q: str, search_type: str, cursor: Optional[str] = None
+ ) -> SocialXPostSearchResult:
"""
- Async variant of search_wikipedia. Search and filter all Wikipedia pages available via the Perigon API. The result includes a list of individual pages that were matched to your specific criteria.
+ Search recent posts on X (Twitter). Each request counts against your social request quota if your plan has one, otherwise against your regular request quota.
Args:
- q (Optional[str]): String. Primary search query for filtering pages based on their title, summary, and content. Supports Boolean operators (AND, OR, NOT), exact phrases with quotes, and wildcards (* and ?) for flexible searching.
- title (Optional[str]): String. Search specifically within page titles. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- summary (Optional[str]): String. Search specifically within page summary. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- text (Optional[str]): String. Search specifically within the page's content (across all sections). Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- reference (Optional[str]): String. Search specifically across page's references. Supports Boolean operators, exact phrases with quotes, and wildcards for matching title variations.
- id (Optional[List[str]]): String Array. Retrieve specific pages by their unique Perigon identifiers. Multiple IDs can be provided to return a collection of specific pages.
- wiki_page_id (Optional[List[int]]): Integer Array. Retrieve specific pages by their Wikipedia identifiers. These are unique only in a combination with `wikiCode` parameter. Multiple IDs can be provided to return a collection of specific pages.
- wiki_revision_id (Optional[List[int]]): Integer Array. Retrieve specific pages by their Wikipedia revision identifiers. These are unique only in a combination with `wikiCode` parameter. Multiple IDs can be provided to return a collection of specific pages. This ID changes each time a page is edited.
- wiki_code (Optional[List[str]]): String Array. Retrieve pages only from specified wiki projects. Currently, the only accepted value is `enwiki`.
- wiki_namespace (Optional[List[int]]): Integer Array. Retrieve pages only from specified wiki namespace. Currently, only the main namespace (0) is available.
- wikidata_id (Optional[List[str]]): String Array. Retrieve pages by the ids corresponding to their Wikidata entities.
- wikidata_instance_of_id (Optional[List[str]]): String Array. Retrieve all pages whose Wikidata entities are instances of these provided ids.
- wikidata_instance_of_label (Optional[List[str]]): String Array. Retrieve all pages whose Wikidata entities are instances of these ids (provided as labels).
- category (Optional[List[str]]): String Array. Retrieve all pages for specified categories.
- section_id (Optional[List[str]]): String Array. Retrieve pages containing provided section ids. Each section ID is unique.
- wiki_revision_from (Optional[datetime]): Date. Retrieve pages modified after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- wiki_revision_to (Optional[datetime]): Date. Retrieve pages modified before this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- scraped_at_from (Optional[datetime]): Date. Retrieve pages scraped after this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- scraped_at_to (Optional[datetime]): Date. Retrieve pages scraped before this date. Accepts ISO 8601 format (e.g., 2023-03-01T00:00:00) or yyyy-mm-dd format.
- pageviews_from (Optional[int]): Integer. Retrieve pages with the average number of views per day higher than the provided value.
- pageviews_to (Optional[int]): Integer. Retrieve pages with the average number of views per day lower than the provided value.
- with_pageviews (Optional[bool]): Boolean. Retrieve pages that have any viewership statistics available for them. If `false` (the default) - return all pages.
- show_num_results (Optional[bool]): Boolean. Whether to show the total number of all matched pages. Default value is false which makes queries a bit more efficient but also counts up to 10000 pages.
- page (Optional[int]): Integer. The specific page of results to retrieve in the paginated response. Starts at 0.
- size (Optional[int]): Integer. The number of articles to return per page in the paginated response.
- sort_by (Optional[str]): String. Determines the Wikipedia page sorting order. Options include relevance (default), revisionTsDesc (recently edited first), revisionTsAsc (recently edited last), pageViewsDesc (highest viewership first), pageViewsAsc (highest viewership last), scrapedAtDesc (recently scraped first), scrapedAtAsc (recently scraped last).
- Allowed values: 'relevance', 'revisionTsDesc', 'revisionTsAsc', 'pageViewsDesc', 'pageViewsAsc', 'scrapedAtDesc', 'scrapedAtAsc'
+ q (str): Search query. Supports X search operators, e.g. `from:perigon`, `#ai` or `\"exact phrase\"`. (required)
+ search_type (str): Which posts to return: top, latest or media posts. (required)
+ Allowed values: 'TOP', 'LATEST', 'MEDIA'
+ cursor (Optional[str]): `nextCursor` from the previous page.
Returns:
- WikipediaSearchResult: The response
+ SocialXPostSearchResult: The response
"""
# Get path template from class attribute
- path = PATH_SEARCH_WIKIPEDIA
+ path = PATH_SEARCH_X_POSTS
params: Dict[str, Any] = {}
if q is not None:
params["q"] = q
- if title is not None:
- params["title"] = title
- if summary is not None:
- params["summary"] = summary
- if text is not None:
- params["text"] = text
- if reference is not None:
- params["reference"] = reference
- if id is not None:
- params["id"] = id
- if wiki_page_id is not None:
- params["wikiPageId"] = wiki_page_id
- if wiki_revision_id is not None:
- params["wikiRevisionId"] = wiki_revision_id
- if wiki_code is not None:
- params["wikiCode"] = wiki_code
- if wiki_namespace is not None:
- params["wikiNamespace"] = wiki_namespace
- if wikidata_id is not None:
- params["wikidataId"] = wikidata_id
- if wikidata_instance_of_id is not None:
- params["wikidataInstanceOfId"] = wikidata_instance_of_id
- if wikidata_instance_of_label is not None:
- params["wikidataInstanceOfLabel"] = wikidata_instance_of_label
- if category is not None:
- params["category"] = category
- if section_id is not None:
- params["sectionId"] = section_id
- if wiki_revision_from is not None:
- params["wikiRevisionFrom"] = wiki_revision_from
- if wiki_revision_to is not None:
- params["wikiRevisionTo"] = wiki_revision_to
- if scraped_at_from is not None:
- params["scrapedAtFrom"] = scraped_at_from
- if scraped_at_to is not None:
- params["scrapedAtTo"] = scraped_at_to
- if pageviews_from is not None:
- params["pageviewsFrom"] = pageviews_from
- if pageviews_to is not None:
- params["pageviewsTo"] = pageviews_to
- if with_pageviews is not None:
- params["withPageviews"] = with_pageviews
- if show_num_results is not None:
- params["showNumResults"] = show_num_results
- if page is not None:
- params["page"] = page
- if size is not None:
- params["size"] = size
- if sort_by is not None:
- params["sortBy"] = sort_by
+ if search_type is not None:
+ params["searchType"] = search_type
+ if cursor is not None:
+ params["cursor"] = cursor
params = _normalise_query(params)
- resp = await self.api_client.request_async("GET", path, params=params)
+ resp = await self.api_client.request("GET", path, params=params)
resp.raise_for_status()
- return WikipediaSearchResult.model_validate(resp.json())
+ return SocialXPostSearchResult.model_validate(resp.json())
- # ----------------- update_source_group (sync) ----------------- #
- def update_source_group(
- self, id: int, patch_source_group_params: PatchSourceGroupParams
- ):
+ # ----------------- update_contact_point (async) ----------------- #
+ async def update_contact_point(
+ self, uuid: UUID, contact_point_update_body: ContactPointUpdateBody
+ ) -> ContactPointResult:
"""
- Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
+ Partially update a contact point owned by the organization associated with the API key. Only provided fields will be updated.
Args:
- id (int): Parameter id (required)
- patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
+ uuid (UUID): Parameter uuid (required)
+ contact_point_update_body (ContactPointUpdateBody): Parameter contact_point_update_body (required)
+ Returns:
+ ContactPointResult: The response
"""
# Get path template from class attribute
- path = PATH_UPDATE_SOURCE_GROUP
+ path = PATH_UPDATE_CONTACT_POINT
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = self.api_client.request(
+ resp = await self.api_client.request(
"PATCH",
path,
params=params,
- json=patch_source_group_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(contact_point_update_body),
)
resp.raise_for_status()
- return resp.json()
+ return ContactPointResult.model_validate(resp.json())
- # ----------------- update_source_group (async) ----------------- #
- async def update_source_group_async(
- self, id: int, patch_source_group_params: PatchSourceGroupParams
- ):
+ # ----------------- update_monitor_api (async) ----------------- #
+ async def update_monitor_api(
+ self, uuid: UUID, signal_api_update_body: SignalApiUpdateBody
+ ) -> MonitorResult:
"""
- Async variant of update_source_group. Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
+ Partially update a monitor. Only provided fields are modified. Use /activate, /pause, or DELETE to change monitor status.
Args:
- id (int): Parameter id (required)
- patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
+ uuid (UUID): UUID of the monitor to update. (required)
+ signal_api_update_body (SignalApiUpdateBody): Parameter signal_api_update_body (required)
+ Returns:
+ MonitorResult: The response
"""
# Get path template from class attribute
- path = PATH_UPDATE_SOURCE_GROUP
+ path = PATH_UPDATE_MONITOR_API
# Replace path parameters
- path = path.format(id=str(id))
+ path = path.format(uuid=str(uuid))
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"PATCH",
path,
params=params,
- json=patch_source_group_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(signal_api_update_body),
)
resp.raise_for_status()
- return resp.json()
+ return MonitorResult.model_validate(resp.json())
- # ----------------- update_watchlist (sync) ----------------- #
- def update_watchlist(self, id: int, update_watchlist_params: UpdateWatchlistParams):
+ # ----------------- update_source_group (async) ----------------- #
+ async def update_source_group(
+ self, id: int, patch_source_group_params: PatchSourceGroupParams
+ ) -> SourceGroupResult:
"""
- Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
+ Partially update a source group owned by the organization associated with the API key. Only provided fields will be updated.
Args:
id (int): Parameter id (required)
- update_watchlist_params (UpdateWatchlistParams): Parameter update_watchlist_params (required)
+ patch_source_group_params (PatchSourceGroupParams): Parameter patch_source_group_params (required)
+ Returns:
+ SourceGroupResult: The response
"""
# Get path template from class attribute
- path = PATH_UPDATE_WATCHLIST
+ path = PATH_UPDATE_SOURCE_GROUP
# Replace path parameters
path = path.format(id=str(id))
- # --- build query dict on the fly ---
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = self.api_client.request(
+ resp = await self.api_client.request(
"PATCH",
path,
params=params,
- json=update_watchlist_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(patch_source_group_params),
)
resp.raise_for_status()
- return resp.json()
+ return SourceGroupResult.model_validate(resp.json())
# ----------------- update_watchlist (async) ----------------- #
- async def update_watchlist_async(
+ async def update_watchlist(
self, id: int, update_watchlist_params: UpdateWatchlistParams
- ):
+ ) -> WatchlistResult:
"""
- Async variant of update_watchlist. Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
+ Partially update a watchlist owned by the organization associated with the API key. Only provided fields will be updated.
Args:
id (int): Parameter id (required)
update_watchlist_params (UpdateWatchlistParams): Parameter update_watchlist_params (required)
+ Returns:
+ WatchlistResult: The response
"""
# Get path template from class attribute
path = PATH_UPDATE_WATCHLIST
@@ -4401,50 +6262,21 @@ async def update_watchlist_async(
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"PATCH",
path,
params=params,
- json=update_watchlist_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return resp.json()
-
- # ----------------- vector_search_articles (sync) ----------------- #
- def vector_search_articles(
- self, article_search_params: ArticleSearchParams
- ) -> ArticlesVectorSearchResult:
- """
- Perform a natural language search over news articles from the past 6 months using semantic relevance. The result includes a list of articles most closely matched to your query intent.
-
- Args:
- article_search_params (ArticleSearchParams): Parameter article_search_params (required)
-
- Returns:
- ArticlesVectorSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_VECTOR_SEARCH_ARTICLES
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "POST",
- path,
- params=params,
- json=article_search_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(update_watchlist_params),
)
resp.raise_for_status()
- return ArticlesVectorSearchResult.model_validate(resp.json())
+ return WatchlistResult.model_validate(resp.json())
# ----------------- vector_search_articles (async) ----------------- #
- async def vector_search_articles_async(
+ async def vector_search_articles(
self, article_search_params: ArticleSearchParams
) -> ArticlesVectorSearchResult:
"""
- Async variant of vector_search_articles. Perform a natural language search over news articles from the past 6 months using semantic relevance. The result includes a list of articles most closely matched to your query intent.
+ Perform a natural language search over news articles from the past 6 months using semantic relevance. The result includes a list of articles most closely matched to your query intent.
Args:
article_search_params (ArticleSearchParams): Parameter article_search_params (required)
@@ -4458,50 +6290,21 @@ async def vector_search_articles_async(
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"POST",
path,
params=params,
- json=article_search_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(article_search_params),
)
resp.raise_for_status()
return ArticlesVectorSearchResult.model_validate(resp.json())
- # ----------------- vector_search_wikipedia (sync) ----------------- #
- def vector_search_wikipedia(
- self, wikipedia_search_params: WikipediaSearchParams
- ) -> WikipediaVectorSearchResult:
- """
- Perform a natural language search over Wikipedia pages using semantic relevance. The result includes a list of page sections most closely matched to your query intent.
-
- Args:
- wikipedia_search_params (WikipediaSearchParams): Parameter wikipedia_search_params (required)
-
- Returns:
- WikipediaVectorSearchResult: The response
- """
- # Get path template from class attribute
- path = PATH_VECTOR_SEARCH_WIKIPEDIA
-
- # --- build query dict on the fly ---
- params: Dict[str, Any] = {}
- params = _normalise_query(params)
-
- resp = self.api_client.request(
- "POST",
- path,
- params=params,
- json=wikipedia_search_params.model_dump(by_alias=True, exclude_none=True),
- )
- resp.raise_for_status()
- return WikipediaVectorSearchResult.model_validate(resp.json())
-
# ----------------- vector_search_wikipedia (async) ----------------- #
- async def vector_search_wikipedia_async(
+ async def vector_search_wikipedia(
self, wikipedia_search_params: WikipediaSearchParams
) -> WikipediaVectorSearchResult:
"""
- Async variant of vector_search_wikipedia. Perform a natural language search over Wikipedia pages using semantic relevance. The result includes a list of page sections most closely matched to your query intent.
+ Perform a natural language search over Wikipedia pages using semantic relevance. The result includes a list of page sections most closely matched to your query intent.
Args:
wikipedia_search_params (WikipediaSearchParams): Parameter wikipedia_search_params (required)
@@ -4515,11 +6318,11 @@ async def vector_search_wikipedia_async(
params: Dict[str, Any] = {}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"POST",
path,
params=params,
- json=wikipedia_search_params.model_dump(by_alias=True, exclude_none=True),
+ json=_json_body(wikipedia_search_params),
)
resp.raise_for_status()
return WikipediaVectorSearchResult.model_validate(resp.json())
diff --git a/src/perigon/api_client.py b/src/perigon/api_client.py
new file mode 100644
index 0000000..3b75c4d
--- /dev/null
+++ b/src/perigon/api_client.py
@@ -0,0 +1,178 @@
+# Package: perigon
+from __future__ import annotations
+
+from typing import Any, TypeVar
+
+import httpx
+
+from perigon.configuration import DEFAULT_BASE_URL, Configuration
+
+# The generator REST client uses this timeout when the caller does not set one.
+DEFAULT_ASYNC_TIMEOUT = 5 * 60
+
+ClientT = TypeVar("ClientT", bound="ApiClient | AsyncApiClient")
+
+
+def _auth_headers(api_key: str | None) -> dict[str, str]:
+ headers: dict[str, str] = {}
+ if not api_key:
+ return headers
+ headers["Authorization"] = f"Bearer {api_key}"
+ return headers
+
+
+def _resolve_configuration(
+ api_key: str | None,
+ base_url: str | None,
+ configuration: Configuration | None,
+) -> Configuration:
+ if configuration is not None and (api_key is not None or base_url is not None):
+ raise ValueError("Pass either configuration or api_key and base_url, not both.")
+ if configuration is not None:
+ return configuration
+ return Configuration(api_key=api_key, base_url=base_url or DEFAULT_BASE_URL)
+
+
+class ApiClient:
+ """Sync HTTP client. Owns one ``httpx.Client`` and one connection pool."""
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+ ) -> None:
+ self.configuration = _resolve_configuration(api_key, base_url, configuration)
+ self.timeout = timeout
+ # The pool is created here. __enter__ does not enter the httpx client,
+ # because httpx only allows that before the first request.
+ self._client = httpx.Client(
+ base_url=self.configuration.base_url, timeout=self.timeout
+ )
+
+ def request(
+ self,
+ method: str,
+ path: str,
+ headers: dict[str, str] | None = None,
+ **kwargs: Any,
+ ) -> httpx.Response:
+ all_headers = _auth_headers(self.configuration.api_key)
+ if headers:
+ all_headers.update(headers)
+ url = f"{self.configuration.base_url}{path}"
+ return self._client.request(method, url, headers=all_headers, **kwargs)
+
+ def __enter__(self) -> ApiClient:
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ self.close()
+
+ def close(self) -> None:
+ self._client.close()
+
+
+class _AsyncRESTClient:
+ """Lazy async pool, following the generator REST client.
+
+ ``pool_manager`` stays ``None`` until the first request, which creates the
+ ``httpx.AsyncClient`` on the running loop.
+ """
+
+ def __init__(self, timeout: float) -> None:
+ self.timeout = timeout
+ self.pool_manager: httpx.AsyncClient | None = None
+
+ async def close(self) -> None:
+ if self.pool_manager is not None:
+ await self.pool_manager.aclose()
+
+ async def request(
+ self,
+ method: str,
+ url: str,
+ headers: dict[str, str] | None = None,
+ **kwargs: Any,
+ ) -> httpx.Response:
+ if self.pool_manager is None:
+ self.pool_manager = httpx.AsyncClient()
+ kwargs.setdefault("timeout", self.timeout)
+ return await self.pool_manager.request(method, url, headers=headers, **kwargs)
+
+
+class AsyncApiClient:
+ """Async HTTP client. The pool is created on the first request."""
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+ ) -> None:
+ self.configuration = _resolve_configuration(api_key, base_url, configuration)
+ self.timeout = DEFAULT_ASYNC_TIMEOUT if timeout is None else timeout
+ self.rest_client = _AsyncRESTClient(self.timeout)
+
+ async def request(
+ self,
+ method: str,
+ path: str,
+ headers: dict[str, str] | None = None,
+ **kwargs: Any,
+ ) -> httpx.Response:
+ all_headers = _auth_headers(self.configuration.api_key)
+ if headers:
+ all_headers.update(headers)
+ url = f"{self.configuration.base_url}{path}"
+ return await self.rest_client.request(
+ method, url, headers=all_headers, **kwargs
+ )
+
+ async def __aenter__(self) -> AsyncApiClient:
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
+ await self.close()
+
+ async def close(self) -> None:
+ await self.rest_client.close()
+
+
+def bind_client(
+ api_client: ClientT | None,
+ client_cls: type[ClientT],
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+) -> tuple[ClientT, bool]:
+ """Return a client and whether the caller owns it.
+
+ A passed client is borrowed. Keyword arguments create a client that the
+ caller must close. Passing both is rejected.
+ """
+ if api_client is not None and (
+ api_key is not None
+ or base_url is not None
+ or timeout is not None
+ or configuration is not None
+ ):
+ raise ValueError(
+ "Pass either an API client or api_key, base_url, timeout, "
+ "and configuration, not both."
+ )
+ if api_client is not None:
+ return api_client, False
+ return (
+ client_cls(
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ ),
+ True,
+ )
diff --git a/perigon/api_response.py b/src/perigon/api_response.py
similarity index 100%
rename from perigon/api_response.py
rename to src/perigon/api_response.py
diff --git a/src/perigon/configuration.py b/src/perigon/configuration.py
new file mode 100644
index 0000000..36ffa4e
--- /dev/null
+++ b/src/perigon/configuration.py
@@ -0,0 +1,20 @@
+# Package: perigon
+"""Settings shared by the sync and async API clients."""
+
+DEFAULT_BASE_URL = "https://api.perigon.io"
+
+
+class Configuration:
+ """API key and host used to build a client.
+
+ :param api_key: Perigon API key. Sent as ``Authorization: Bearer``.
+ :param base_url: API host. Defaults to ``https://api.perigon.io``.
+ """
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str = DEFAULT_BASE_URL,
+ ) -> None:
+ self.api_key = api_key
+ self.base_url = base_url or DEFAULT_BASE_URL
diff --git a/perigon/exceptions.py b/src/perigon/exceptions.py
similarity index 96%
rename from perigon/exceptions.py
rename to src/perigon/exceptions.py
index 94cc8ad..632572c 100644
--- a/perigon/exceptions.py
+++ b/src/perigon/exceptions.py
@@ -1,5 +1,3 @@
-# coding: utf-8
-
"""
Perigon API
@@ -10,7 +8,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
+"""
from typing import Any, Optional
@@ -106,7 +104,6 @@ def __init__(self, msg, path_to_item=None) -> None:
class ApiException(OpenApiException):
-
def __init__(
self,
status=None,
@@ -132,7 +129,7 @@ def __init__(
self.body = http_resp.data.decode("utf-8")
except Exception:
pass
- self.headers = http_resp.getheaders()
+ self.headers = http_resp.headers
@classmethod
def from_response(
@@ -169,12 +166,15 @@ def from_response(
def __str__(self):
"""Custom error messages for exception"""
- error_message = "({0})\n" "Reason: {1}\n".format(self.status, self.reason)
+ error_message = "({0})\nReason: {1}\n".format(self.status, self.reason)
if self.headers:
error_message += "HTTP response headers: {0}\n".format(self.headers)
- if self.data or self.body:
- error_message += "HTTP response body: {0}\n".format(self.data or self.body)
+ if self.body:
+ error_message += "HTTP response body: {0}\n".format(self.body)
+
+ if self.data:
+ error_message += "HTTP response data: {0}\n".format(self.data)
return error_message
diff --git a/perigon/models/__init__.py b/src/perigon/models/__init__.py
similarity index 50%
rename from perigon/models/__init__.py
rename to src/perigon/models/__init__.py
index d111d97..e2df4fd 100644
--- a/perigon/models/__init__.py
+++ b/src/perigon/models/__init__.py
@@ -13,37 +13,60 @@
Do not edit the class manually.
""" # noqa: E501
-
# import models into model package
from perigon.models.all_endpoint_sort_by import AllEndpointSortBy
+from perigon.models.api_key_status import ApiKeyStatus
+from perigon.models.api_limits_dto import ApiLimitsDto
from perigon.models.article import Article
+from perigon.models.article_news_cluster import ArticleNewsCluster
from perigon.models.article_search_filter import ArticleSearchFilter
from perigon.models.article_search_params import ArticleSearchParams
from perigon.models.articles_vector_search_result import ArticlesVectorSearchResult
from perigon.models.category_holder import CategoryHolder
from perigon.models.category_with_score_holder import CategoryWithScoreHolder
+from perigon.models.clusters_list_sort_by import ClustersListSortBy
from perigon.models.company import Company
from perigon.models.company_count import CompanyCount
from perigon.models.company_holder import CompanyHolder
from perigon.models.company_search_result import CompanySearchResult
+from perigon.models.complex_all_endpoint_query import ComplexAllEndpointQuery
+from perigon.models.contact_point_api_dto import ContactPointApiDto
+from perigon.models.contact_point_list_result import ContactPointListResult
+from perigon.models.contact_point_request_body import ContactPointRequestBody
+from perigon.models.contact_point_result import ContactPointResult
+from perigon.models.contact_point_sort_by import ContactPointSortBy
+from perigon.models.contact_point_update_body import ContactPointUpdateBody
from perigon.models.coordinate import Coordinate
from perigon.models.coordinate_filter import CoordinateFilter
from perigon.models.create_source_group_params import CreateSourceGroupParams
from perigon.models.create_watchlist_params import CreateWatchlistParams
+from perigon.models.entity_group import EntityGroup
from perigon.models.entity_holder import EntityHolder
from perigon.models.error_response import ErrorResponse
from perigon.models.event_type_holder import EventTypeHolder
+from perigon.models.fillbot_metadata import FillbotMetadata
+from perigon.models.filled_field import FilledField
+from perigon.models.filled_field_source import FilledFieldSource
from perigon.models.id_name_holder import IdNameHolder
from perigon.models.image_holder import ImageHolder
from perigon.models.journalist import Journalist
from perigon.models.journalist_search_result import JournalistSearchResult
+from perigon.models.journalists_sort_by import JournalistsSortBy
from perigon.models.key_point import KeyPoint
from perigon.models.keyword_holder import KeywordHolder
from perigon.models.label_holder import LabelHolder
+from perigon.models.limits_result import LimitsResult
from perigon.models.location_count import LocationCount
from perigon.models.location_holder import LocationHolder
+from perigon.models.matched_entity_data import MatchedEntityData
+from perigon.models.monitor_event_list_result import MonitorEventListResult
+from perigon.models.monitor_list_result import MonitorListResult
+from perigon.models.monitor_newsletter_list_result import MonitorNewsletterListResult
+from perigon.models.monitor_result import MonitorResult
+from perigon.models.monitor_summary_list_result import MonitorSummaryListResult
from perigon.models.name_count import NameCount
from perigon.models.news_cluster import NewsCluster
+from perigon.models.news_cluster_article import NewsClusterArticle
from perigon.models.patch_source_group_params import PatchSourceGroupParams
from perigon.models.people_search_result import PeopleSearchResult
from perigon.models.person import Person
@@ -53,15 +76,65 @@
from perigon.models.query_search_result import QuerySearchResult
from perigon.models.question import Question
from perigon.models.record_stat_holder import RecordStatHolder
+from perigon.models.schedule_interval import ScheduleInterval
from perigon.models.scored_data_article import ScoredDataArticle
from perigon.models.scored_data_wiki_data import ScoredDataWikiData
from perigon.models.sentiment_holder import SentimentHolder
-from perigon.models.sort_by import SortBy
+from perigon.models.signal_api_create_body import SignalApiCreateBody
+from perigon.models.signal_api_dto import SignalApiDto
+from perigon.models.signal_api_update_body import SignalApiUpdateBody
+from perigon.models.signal_article_partial_dto import SignalArticlePartialDto
+from perigon.models.signal_event_dto import SignalEventDto
+from perigon.models.signal_linked_in_query import SignalLinkedInQuery
+from perigon.models.signal_monitoring_plan_data_schema import (
+ SignalMonitoringPlanDataSchema,
+)
+from perigon.models.signal_monitoring_plan_data_schema_property import (
+ SignalMonitoringPlanDataSchemaProperty,
+)
+from perigon.models.signal_monitoring_plan_newsletter_config import (
+ SignalMonitoringPlanNewsletterConfig,
+)
+from perigon.models.signal_newsletter_dto import SignalNewsletterDto
+from perigon.models.signal_newsletter_story_partial_dto import (
+ SignalNewsletterStoryPartialDto,
+)
+from perigon.models.signal_reddit_query import SignalRedditQuery
+from perigon.models.signal_schedule_policy import SignalSchedulePolicy
+from perigon.models.signal_summary_dto import SignalSummaryDto
+from perigon.models.signal_x_query import SignalXQuery
+from perigon.models.social_linked_in_feed_post import SocialLinkedInFeedPost
+from perigon.models.social_linked_in_feed_post_author import (
+ SocialLinkedInFeedPostAuthor,
+)
+from perigon.models.social_linked_in_feed_post_comment import (
+ SocialLinkedInFeedPostComment,
+)
+from perigon.models.social_linked_in_feed_post_reaction import (
+ SocialLinkedInFeedPostReaction,
+)
+from perigon.models.social_linked_in_feed_result import SocialLinkedInFeedResult
+from perigon.models.social_linked_in_post import SocialLinkedInPost
+from perigon.models.social_linked_in_post_author import SocialLinkedInPostAuthor
+from perigon.models.social_linked_in_post_search_result import (
+ SocialLinkedInPostSearchResult,
+)
+from perigon.models.social_reddit_post import SocialRedditPost
+from perigon.models.social_reddit_post_search_result import SocialRedditPostSearchResult
+from perigon.models.social_x_post import SocialXPost
+from perigon.models.social_x_post_author import SocialXPostAuthor
+from perigon.models.social_x_post_search_result import SocialXPostSearchResult
from perigon.models.source import Source
+from perigon.models.source_group_dto import SourceGroupDto
+from perigon.models.source_group_list_result import SourceGroupListResult
+from perigon.models.source_group_resolve_result import SourceGroupResolveResult
+from perigon.models.source_group_result import SourceGroupResult
+from perigon.models.source_group_sort_by import SourceGroupSortBy
from perigon.models.source_holder import SourceHolder
from perigon.models.source_location import SourceLocation
from perigon.models.source_search_result import SourceSearchResult
from perigon.models.source_top_stat_holder import SourceTopStatHolder
+from perigon.models.sources_sort_by import SourcesSortBy
from perigon.models.stat_result import StatResult
from perigon.models.story_history_record import StoryHistoryRecord
from perigon.models.story_history_result import StoryHistoryResult
@@ -74,9 +147,15 @@
from perigon.models.topic_labels import TopicLabels
from perigon.models.topic_search_result import TopicSearchResult
from perigon.models.update_watchlist_params import UpdateWatchlistParams
-from perigon.models.vector_data import VectorData
from perigon.models.watchlist_company import WatchlistCompany
+from perigon.models.watchlist_company_dto import WatchlistCompanyDto
+from perigon.models.watchlist_dto import WatchlistDto
+from perigon.models.watchlist_list_result import WatchlistListResult
from perigon.models.watchlist_person import WatchlistPerson
+from perigon.models.watchlist_person_dto import WatchlistPersonDto
+from perigon.models.watchlist_resolve_result import WatchlistResolveResult
+from perigon.models.watchlist_result import WatchlistResult
+from perigon.models.watchlist_sort_by import WatchlistSortBy
from perigon.models.web_resources import WebResources
from perigon.models.wiki_data import WikiData
from perigon.models.wiki_page import WikiPage
@@ -89,3 +168,6 @@
from perigon.models.wikipedia_search_params import WikipediaSearchParams
from perigon.models.wikipedia_search_result import WikipediaSearchResult
from perigon.models.wikipedia_vector_search_result import WikipediaVectorSearchResult
+from perigon.models.x_post import XPost
+from perigon.models.x_post_media import XPostMedia
+from perigon.models.x_post_media_item import XPostMediaItem
diff --git a/perigon/models/all_endpoint_sort_by.py b/src/perigon/models/all_endpoint_sort_by.py
similarity index 98%
rename from perigon/models/all_endpoint_sort_by.py
rename to src/perigon/models/all_endpoint_sort_by.py
index 25d6723..2c81b70 100644
--- a/perigon/models/all_endpoint_sort_by.py
+++ b/src/perigon/models/all_endpoint_sort_by.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
diff --git a/src/perigon/models/api_key_status.py b/src/perigon/models/api_key_status.py
new file mode 100644
index 0000000..6ba8247
--- /dev/null
+++ b/src/perigon/models/api_key_status.py
@@ -0,0 +1,148 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class ApiKeyStatus(BaseModel):
+ """
+ Validity and status of a single API key.
+ """
+
+ key_preview: Optional[StrictStr] = Field(
+ default=None,
+ description="Masked preview of the supplied API key.",
+ alias="keyPreview",
+ )
+ valid: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the supplied API key is valid and belongs to the authenticated account.",
+ )
+ enabled: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the API key is enabled. A disabled ('off') key is valid but cannot be used to make requests. False when the key is not valid.",
+ )
+ subscription_active: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the account owning the key has an active subscription or trial.",
+ alias="subscriptionActive",
+ )
+ request_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Approx number of requests made with this API key in this billing cycle.",
+ alias="requestCount",
+ )
+ __properties: ClassVar[List[str]] = [
+ "keyPreview",
+ "valid",
+ "enabled",
+ "subscriptionActive",
+ "requestCount",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ApiKeyStatus from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if key_preview (nullable) is None
+ # and model_fields_set contains the field
+ if self.key_preview is None and "key_preview" in self.model_fields_set:
+ _dict["keyPreview"] = None
+
+ # set to None if valid (nullable) is None
+ # and model_fields_set contains the field
+ if self.valid is None and "valid" in self.model_fields_set:
+ _dict["valid"] = None
+
+ # set to None if enabled (nullable) is None
+ # and model_fields_set contains the field
+ if self.enabled is None and "enabled" in self.model_fields_set:
+ _dict["enabled"] = None
+
+ # set to None if subscription_active (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.subscription_active is None
+ and "subscription_active" in self.model_fields_set
+ ):
+ _dict["subscriptionActive"] = None
+
+ # set to None if request_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.request_count is None and "request_count" in self.model_fields_set:
+ _dict["requestCount"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ApiKeyStatus from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "keyPreview": obj.get("keyPreview"),
+ "valid": obj.get("valid"),
+ "enabled": obj.get("enabled"),
+ "subscriptionActive": obj.get("subscriptionActive"),
+ "requestCount": obj.get("requestCount"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/api_limits_dto.py b/src/perigon/models/api_limits_dto.py
new file mode 100644
index 0000000..dd35506
--- /dev/null
+++ b/src/perigon/models/api_limits_dto.py
@@ -0,0 +1,246 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.api_key_status import ApiKeyStatus
+
+
+class ApiLimitsDto(BaseModel):
+ """
+ Current API usage and limits for the authenticated account, optionally including the status of additional API keys that were requested. Reading this does not count against the account's request quota or rate limit.
+ """
+
+ organization_name: Optional[StrictStr] = Field(
+ default=None,
+ description="Name of the account (organization) the API key belongs to.",
+ alias="organizationName",
+ )
+ requests_used: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of billable requests used in the current tracking period.",
+ alias="requestsUsed",
+ )
+ request_limit: Optional[StrictInt] = Field(
+ default=None,
+ description="Maximum number of requests allowed in the current tracking period. Null means the account has no request limit.",
+ alias="requestLimit",
+ )
+ max_page_size: Optional[StrictInt] = Field(
+ default=None,
+ description="Maximum number of results allowed per page (page size).",
+ alias="maxPageSize",
+ )
+ pagination_limit: Optional[StrictInt] = Field(
+ default=None,
+ description="Maximum number of results that can be paginated through.",
+ alias="paginationLimit",
+ )
+ article_content_truncation: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of characters article content is truncated to. Null means content is not truncated.",
+ alias="articleContentTruncation",
+ )
+ since: Optional[datetime] = Field(
+ default=None, description="Start of the current tracking period."
+ )
+ last_made_at: Optional[datetime] = Field(
+ default=None,
+ description="Timestamp of the last billable request.",
+ alias="lastMadeAt",
+ )
+ reset_at: Optional[datetime] = Field(
+ default=None,
+ description="When the request counter resets to zero.",
+ alias="resetAt",
+ )
+ subscription_cancel_at: Optional[datetime] = Field(
+ default=None,
+ description="Date the API subscription is scheduled to be cancelled. Absent when the subscription is not set to cancel.",
+ alias="subscriptionCancelAt",
+ )
+ key_statuses: Optional[List[ApiKeyStatus]] = Field(
+ default=None,
+ description="Status of each API key supplied via the 'apiKeys' parameter, in the same order as the request. Absent when no keys were supplied.",
+ alias="keyStatuses",
+ )
+ __properties: ClassVar[List[str]] = [
+ "organizationName",
+ "requestsUsed",
+ "requestLimit",
+ "maxPageSize",
+ "paginationLimit",
+ "articleContentTruncation",
+ "since",
+ "lastMadeAt",
+ "resetAt",
+ "subscriptionCancelAt",
+ "keyStatuses",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ApiLimitsDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in key_statuses (list)
+ _items = []
+ if self.key_statuses:
+ for _item_key_statuses in self.key_statuses:
+ _items.append(
+ _item_key_statuses.to_dict()
+ if _item_key_statuses is not None
+ else None
+ )
+ _dict["keyStatuses"] = _items
+ # set to None if organization_name (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.organization_name is None
+ and "organization_name" in self.model_fields_set
+ ):
+ _dict["organizationName"] = None
+
+ # set to None if requests_used (nullable) is None
+ # and model_fields_set contains the field
+ if self.requests_used is None and "requests_used" in self.model_fields_set:
+ _dict["requestsUsed"] = None
+
+ # set to None if request_limit (nullable) is None
+ # and model_fields_set contains the field
+ if self.request_limit is None and "request_limit" in self.model_fields_set:
+ _dict["requestLimit"] = None
+
+ # set to None if max_page_size (nullable) is None
+ # and model_fields_set contains the field
+ if self.max_page_size is None and "max_page_size" in self.model_fields_set:
+ _dict["maxPageSize"] = None
+
+ # set to None if pagination_limit (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.pagination_limit is None
+ and "pagination_limit" in self.model_fields_set
+ ):
+ _dict["paginationLimit"] = None
+
+ # set to None if article_content_truncation (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.article_content_truncation is None
+ and "article_content_truncation" in self.model_fields_set
+ ):
+ _dict["articleContentTruncation"] = None
+
+ # set to None if since (nullable) is None
+ # and model_fields_set contains the field
+ if self.since is None and "since" in self.model_fields_set:
+ _dict["since"] = None
+
+ # set to None if last_made_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.last_made_at is None and "last_made_at" in self.model_fields_set:
+ _dict["lastMadeAt"] = None
+
+ # set to None if reset_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.reset_at is None and "reset_at" in self.model_fields_set:
+ _dict["resetAt"] = None
+
+ # set to None if subscription_cancel_at (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.subscription_cancel_at is None
+ and "subscription_cancel_at" in self.model_fields_set
+ ):
+ _dict["subscriptionCancelAt"] = None
+
+ # set to None if key_statuses (nullable) is None
+ # and model_fields_set contains the field
+ if self.key_statuses is None and "key_statuses" in self.model_fields_set:
+ _dict["keyStatuses"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ApiLimitsDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "organizationName": obj.get("organizationName"),
+ "requestsUsed": obj.get("requestsUsed"),
+ "requestLimit": obj.get("requestLimit"),
+ "maxPageSize": obj.get("maxPageSize"),
+ "paginationLimit": obj.get("paginationLimit"),
+ "articleContentTruncation": obj.get("articleContentTruncation"),
+ "since": obj.get("since"),
+ "lastMadeAt": obj.get("lastMadeAt"),
+ "resetAt": obj.get("resetAt"),
+ "subscriptionCancelAt": obj.get("subscriptionCancelAt"),
+ "keyStatuses": [
+ ApiKeyStatus.from_dict(_item) for _item in obj["keyStatuses"]
+ ]
+ if obj.get("keyStatuses") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/perigon/models/article.py b/src/perigon/models/article.py
similarity index 63%
rename from perigon/models/article.py
rename to src/perigon/models/article.py
index 32753d7..0b2b2bc 100644
--- a/perigon/models/article.py
+++ b/src/perigon/models/article.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -29,8 +28,10 @@
StrictInt,
StrictStr,
)
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
+from perigon.models.article_news_cluster import ArticleNewsCluster
from perigon.models.category_holder import CategoryHolder
from perigon.models.category_with_score_holder import CategoryWithScoreHolder
from perigon.models.company_holder import CompanyHolder
@@ -51,62 +52,171 @@
class Article(BaseModel):
"""
Article
- """ # noqa: E501
+ """
- url: Optional[StrictStr] = None
- authors_byline: Optional[StrictStr] = Field(default=None, alias="authorsByline")
- article_id: Optional[StrictStr] = Field(default=None, alias="articleId")
- cluster_id: Optional[StrictStr] = Field(default=None, alias="clusterId")
+ url: Optional[StrictStr] = Field(
+ default=None, description="Canonical URL of the article."
+ )
+ authors_byline: Optional[StrictStr] = Field(
+ default=None,
+ description="Author byline as published by the source.",
+ alias="authorsByline",
+ )
+ article_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Unique Perigon identifier for the article.",
+ alias="articleId",
+ )
+ cluster_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier of the story cluster containing the article, when assigned.",
+ alias="clusterId",
+ )
source: Optional[SourceHolder] = None
- image_url: Optional[StrictStr] = Field(default=None, alias="imageUrl")
- country: Optional[StrictStr] = None
- language: Optional[StrictStr] = None
- pub_date: Optional[StrictStr] = Field(default=None, alias="pubDate")
- add_date: Optional[StrictStr] = Field(default=None, alias="addDate")
- refresh_date: Optional[StrictStr] = Field(default=None, alias="refreshDate")
- score: Optional[Union[StrictFloat, StrictInt]] = None
- title: Optional[StrictStr] = None
- description: Optional[StrictStr] = None
- content: Optional[StrictStr] = None
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Primary image URL associated with the article.",
+ alias="imageUrl",
+ )
+ country: Optional[StrictStr] = Field(
+ default=None,
+ description="Two-character country code associated with the article.",
+ )
+ language: Optional[StrictStr] = Field(
+ default=None, description="ISO 639 two-letter language code for the article."
+ )
+ pub_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was published, in ISO 8601 format.",
+ alias="pubDate",
+ )
+ add_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was added to Perigon, in ISO 8601 format.",
+ alias="addDate",
+ )
+ refresh_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was last refreshed by Perigon, in ISO 8601 format.",
+ alias="refreshDate",
+ )
+ score: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Search relevance score for the article, when applicable.",
+ )
+ title: Optional[StrictStr] = Field(default=None, description="Article headline.")
+ description: Optional[StrictStr] = Field(
+ default=None, description="Article description or publisher-provided excerpt."
+ )
+ content: Optional[StrictStr] = Field(
+ default=None, description="Full article body available to Perigon."
+ )
en_content_word_count: Optional[StrictInt] = Field(
- default=None, alias="enContentWordCount"
+ default=None,
+ description="Word count of the English article content, when available.",
+ alias="enContentWordCount",
+ )
+ medium: Optional[StrictStr] = Field(
+ default=None, description="Primary medium type, such as Article or Video."
+ )
+ links: Optional[List[StrictStr]] = Field(
+ default=None, description="URLs linked from the article content."
+ )
+ labels: Optional[List[LabelHolder]] = Field(
+ default=None, description="Editorial labels assigned to the article."
)
- medium: Optional[StrictStr] = None
- links: Optional[List[StrictStr]] = None
- labels: Optional[List[LabelHolder]] = None
event_types: Optional[List[EventTypeHolder]] = Field(
- default=None, alias="eventTypes"
+ default=None,
+ description="Event types detected in the article.",
+ alias="eventTypes",
)
matched_authors: Optional[List[IdNameHolder]] = Field(
- default=None, alias="matchedAuthors"
- )
- claim: Optional[StrictStr] = None
- verdict: Optional[StrictStr] = None
- keywords: Optional[List[KeywordHolder]] = None
- topics: Optional[List[TopicHolder]] = None
- categories: Optional[List[CategoryHolder]] = None
- taxonomies: Optional[List[CategoryWithScoreHolder]] = None
- entities: Optional[List[EntityHolder]] = None
- companies: Optional[List[CompanyHolder]] = None
+ default=None,
+ description="Known journalist records matched to the article byline.",
+ alias="matchedAuthors",
+ )
+ claim: Optional[StrictStr] = Field(
+ default=None,
+ description="Claim identified in fact-check content, when applicable.",
+ )
+ verdict: Optional[StrictStr] = Field(
+ default=None,
+ description="Verdict identified in fact-check content, when applicable.",
+ )
+ keywords: Optional[List[KeywordHolder]] = Field(
+ default=None,
+ description="Keywords extracted from the article with their relevance weights.",
+ )
+ topics: Optional[List[TopicHolder]] = Field(
+ default=None, description="Topics assigned to the article."
+ )
+ categories: Optional[List[CategoryHolder]] = Field(
+ default=None, description="Broad content categories assigned to the article."
+ )
+ taxonomies: Optional[List[CategoryWithScoreHolder]] = Field(
+ default=None,
+ description="Google Content Categories assigned to the article with confidence scores.",
+ )
+ entities: Optional[List[EntityHolder]] = Field(
+ default=None, description="Named entities extracted from the article."
+ )
+ companies: Optional[List[CompanyHolder]] = Field(
+ default=None, description="Companies identified in the article."
+ )
sentiment: Optional[SentimentHolder] = None
- summary: Optional[StrictStr] = None
- short_summary: Optional[StrictStr] = Field(default=None, alias="shortSummary")
- translation: Optional[StrictStr] = None
- translated_title: Optional[StrictStr] = Field(default=None, alias="translatedTitle")
+ summary: Optional[StrictStr] = Field(
+ default=None, description="Generated summary of the article, when available."
+ )
+ short_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Short generated summary of the article, when available.",
+ alias="shortSummary",
+ )
+ translation: Optional[StrictStr] = Field(
+ default=None, description="Translated article content, when available."
+ )
+ translated_title: Optional[StrictStr] = Field(
+ default=None,
+ description="English translation of the article title, when available.",
+ alias="translatedTitle",
+ )
translated_description: Optional[StrictStr] = Field(
- default=None, alias="translatedDescription"
+ default=None,
+ description="English translation of the article description, when available.",
+ alias="translatedDescription",
)
translated_summary: Optional[StrictStr] = Field(
- default=None, alias="translatedSummary"
- )
- locations: Optional[List[LocationHolder]] = None
- reprint: Optional[StrictBool] = None
- reprint_group_id: Optional[StrictStr] = Field(default=None, alias="reprintGroupId")
- places: Optional[List[Place]] = None
- people: Optional[List[PersonHolder]] = None
- cluster: Optional[NewsCluster] = None
- journalists: Optional[List[Journalist]] = None
- highlights: Optional[Dict[str, List[StrictStr]]] = None
+ default=None,
+ description="English translation of the article summary, when available.",
+ alias="translatedSummary",
+ )
+ locations: Optional[List[LocationHolder]] = Field(
+ default=None, description="Locations identified as central to the article."
+ )
+ reprint: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the article is a reprint of previously published content.",
+ )
+ reprint_group_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier grouping the original article with its known reprints.",
+ alias="reprintGroupId",
+ )
+ places: Optional[List[Place]] = Field(
+ default=None,
+ description="Structured geographic places extracted from the article.",
+ )
+ people: Optional[List[PersonHolder]] = Field(
+ default=None, description="Known people identified in the article."
+ )
+ cluster: Optional[ArticleNewsCluster] = None
+ journalists: Optional[List[Journalist]] = Field(
+ default=None,
+ description="Expanded journalist details, when requested and available.",
+ )
+ highlights: Optional[Dict[str, List[StrictStr]]] = Field(
+ default=None, description="Matched text fragments grouped by article field."
+ )
__properties: ClassVar[List[str]] = [
"url",
"authorsByline",
@@ -155,7 +265,8 @@ class Article(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -166,8 +277,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -198,64 +308,77 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.labels:
for _item_labels in self.labels:
- if _item_labels:
- _items.append(_item_labels.to_dict())
+ _items.append(
+ _item_labels.to_dict() if _item_labels is not None else None
+ )
_dict["labels"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in event_types (list)
_items = []
if self.event_types:
for _item_event_types in self.event_types:
- if _item_event_types:
- _items.append(_item_event_types.to_dict())
+ _items.append(
+ _item_event_types.to_dict()
+ if _item_event_types is not None
+ else None
+ )
_dict["eventTypes"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in matched_authors (list)
_items = []
if self.matched_authors:
for _item_matched_authors in self.matched_authors:
- if _item_matched_authors:
- _items.append(_item_matched_authors.to_dict())
+ _items.append(
+ _item_matched_authors.to_dict()
+ if _item_matched_authors is not None
+ else None
+ )
_dict["matchedAuthors"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in keywords (list)
_items = []
if self.keywords:
for _item_keywords in self.keywords:
- if _item_keywords:
- _items.append(_item_keywords.to_dict())
+ _items.append(
+ _item_keywords.to_dict() if _item_keywords is not None else None
+ )
_dict["keywords"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in topics (list)
_items = []
if self.topics:
for _item_topics in self.topics:
- if _item_topics:
- _items.append(_item_topics.to_dict())
+ _items.append(
+ _item_topics.to_dict() if _item_topics is not None else None
+ )
_dict["topics"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in categories (list)
_items = []
if self.categories:
for _item_categories in self.categories:
- if _item_categories:
- _items.append(_item_categories.to_dict())
+ _items.append(
+ _item_categories.to_dict() if _item_categories is not None else None
+ )
_dict["categories"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in taxonomies (list)
_items = []
if self.taxonomies:
for _item_taxonomies in self.taxonomies:
- if _item_taxonomies:
- _items.append(_item_taxonomies.to_dict())
+ _items.append(
+ _item_taxonomies.to_dict() if _item_taxonomies is not None else None
+ )
_dict["taxonomies"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in entities (list)
_items = []
if self.entities:
for _item_entities in self.entities:
- if _item_entities:
- _items.append(_item_entities.to_dict())
+ _items.append(
+ _item_entities.to_dict() if _item_entities is not None else None
+ )
_dict["entities"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in companies (list)
_items = []
if self.companies:
for _item_companies in self.companies:
- if _item_companies:
- _items.append(_item_companies.to_dict())
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
_dict["companies"] = _items
# override the default output from pydantic by calling `to_dict()` of sentiment
if self.sentiment:
@@ -264,22 +387,25 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.locations:
for _item_locations in self.locations:
- if _item_locations:
- _items.append(_item_locations.to_dict())
+ _items.append(
+ _item_locations.to_dict() if _item_locations is not None else None
+ )
_dict["locations"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in places (list)
_items = []
if self.places:
for _item_places in self.places:
- if _item_places:
- _items.append(_item_places.to_dict())
+ _items.append(
+ _item_places.to_dict() if _item_places is not None else None
+ )
_dict["places"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in people (list)
_items = []
if self.people:
for _item_people in self.people:
- if _item_people:
- _items.append(_item_people.to_dict())
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
_dict["people"] = _items
# override the default output from pydantic by calling `to_dict()` of cluster
if self.cluster:
@@ -288,8 +414,11 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.journalists:
for _item_journalists in self.journalists:
- if _item_journalists:
- _items.append(_item_journalists.to_dict())
+ _items.append(
+ _item_journalists.to_dict()
+ if _item_journalists is not None
+ else None
+ )
_dict["journalists"] = _items
# set to None if url (nullable) is None
# and model_fields_set contains the field
@@ -543,11 +672,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"authorsByline": obj.get("authorsByline"),
"articleId": obj.get("articleId"),
"clusterId": obj.get("clusterId"),
- "source": (
- SourceHolder.from_dict(obj["source"])
- if obj.get("source") is not None
- else None
- ),
+ "source": SourceHolder.from_dict(obj["source"])
+ if obj.get("source") is not None
+ else None,
"imageUrl": obj.get("imageUrl"),
"country": obj.get("country"),
"language": obj.get("language"),
@@ -561,101 +688,79 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"enContentWordCount": obj.get("enContentWordCount"),
"medium": obj.get("medium"),
"links": obj.get("links"),
- "labels": (
- [LabelHolder.from_dict(_item) for _item in obj["labels"]]
- if obj.get("labels") is not None
- else None
- ),
- "eventTypes": (
- [EventTypeHolder.from_dict(_item) for _item in obj["eventTypes"]]
- if obj.get("eventTypes") is not None
- else None
- ),
- "matchedAuthors": (
- [IdNameHolder.from_dict(_item) for _item in obj["matchedAuthors"]]
- if obj.get("matchedAuthors") is not None
- else None
- ),
+ "labels": [LabelHolder.from_dict(_item) for _item in obj["labels"]]
+ if obj.get("labels") is not None
+ else None,
+ "eventTypes": [
+ EventTypeHolder.from_dict(_item) for _item in obj["eventTypes"]
+ ]
+ if obj.get("eventTypes") is not None
+ else None,
+ "matchedAuthors": [
+ IdNameHolder.from_dict(_item) for _item in obj["matchedAuthors"]
+ ]
+ if obj.get("matchedAuthors") is not None
+ else None,
"claim": obj.get("claim"),
"verdict": obj.get("verdict"),
- "keywords": (
- [KeywordHolder.from_dict(_item) for _item in obj["keywords"]]
- if obj.get("keywords") is not None
- else None
- ),
- "topics": (
- [TopicHolder.from_dict(_item) for _item in obj["topics"]]
- if obj.get("topics") is not None
- else None
- ),
- "categories": (
- [CategoryHolder.from_dict(_item) for _item in obj["categories"]]
- if obj.get("categories") is not None
- else None
- ),
- "taxonomies": (
- [
- CategoryWithScoreHolder.from_dict(_item)
- for _item in obj["taxonomies"]
- ]
- if obj.get("taxonomies") is not None
- else None
- ),
- "entities": (
- [EntityHolder.from_dict(_item) for _item in obj["entities"]]
- if obj.get("entities") is not None
- else None
- ),
- "companies": (
- [CompanyHolder.from_dict(_item) for _item in obj["companies"]]
- if obj.get("companies") is not None
- else None
- ),
- "sentiment": (
- SentimentHolder.from_dict(obj["sentiment"])
- if obj.get("sentiment") is not None
- else None
- ),
+ "keywords": [
+ KeywordHolder.from_dict(_item) for _item in obj["keywords"]
+ ]
+ if obj.get("keywords") is not None
+ else None,
+ "topics": [TopicHolder.from_dict(_item) for _item in obj["topics"]]
+ if obj.get("topics") is not None
+ else None,
+ "categories": [
+ CategoryHolder.from_dict(_item) for _item in obj["categories"]
+ ]
+ if obj.get("categories") is not None
+ else None,
+ "taxonomies": [
+ CategoryWithScoreHolder.from_dict(_item)
+ for _item in obj["taxonomies"]
+ ]
+ if obj.get("taxonomies") is not None
+ else None,
+ "entities": [EntityHolder.from_dict(_item) for _item in obj["entities"]]
+ if obj.get("entities") is not None
+ else None,
+ "companies": [
+ CompanyHolder.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "sentiment": SentimentHolder.from_dict(obj["sentiment"])
+ if obj.get("sentiment") is not None
+ else None,
"summary": obj.get("summary"),
"shortSummary": obj.get("shortSummary"),
"translation": obj.get("translation"),
"translatedTitle": obj.get("translatedTitle"),
"translatedDescription": obj.get("translatedDescription"),
"translatedSummary": obj.get("translatedSummary"),
- "locations": (
- [LocationHolder.from_dict(_item) for _item in obj["locations"]]
- if obj.get("locations") is not None
- else None
- ),
+ "locations": [
+ LocationHolder.from_dict(_item) for _item in obj["locations"]
+ ]
+ if obj.get("locations") is not None
+ else None,
"reprint": obj.get("reprint"),
"reprintGroupId": obj.get("reprintGroupId"),
- "places": (
- [Place.from_dict(_item) for _item in obj["places"]]
- if obj.get("places") is not None
- else None
- ),
- "people": (
- [PersonHolder.from_dict(_item) for _item in obj["people"]]
- if obj.get("people") is not None
- else None
- ),
- "cluster": (
- NewsCluster.from_dict(obj["cluster"])
- if obj.get("cluster") is not None
- else None
- ),
- "journalists": (
- [Journalist.from_dict(_item) for _item in obj["journalists"]]
- if obj.get("journalists") is not None
- else None
- ),
+ "places": [Place.from_dict(_item) for _item in obj["places"]]
+ if obj.get("places") is not None
+ else None,
+ "people": [PersonHolder.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "cluster": ArticleNewsCluster.from_dict(obj["cluster"])
+ if obj.get("cluster") is not None
+ else None,
+ "journalists": [
+ Journalist.from_dict(_item) for _item in obj["journalists"]
+ ]
+ if obj.get("journalists") is not None
+ else None,
"highlights": obj.get("highlights"),
}
)
return _obj
-
-
-from perigon.models.news_cluster import NewsCluster
-
-# TODO: Rewrite to not use raise_errors
-Article.model_rebuild(raise_errors=False)
diff --git a/perigon/models/news_cluster.py b/src/perigon/models/article_news_cluster.py
similarity index 61%
rename from perigon/models/news_cluster.py
rename to src/perigon/models/article_news_cluster.py
index 1f4e2e0..422f075 100644
--- a/perigon/models/news_cluster.py
+++ b/src/perigon/models/article_news_cluster.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.category_holder import CategoryHolder
@@ -38,62 +38,159 @@
from perigon.models.topic_holder import TopicHolder
-class NewsCluster(BaseModel):
+class ArticleNewsCluster(BaseModel):
"""
- NewsCluster
- """ # noqa: E501
-
- created_at: Optional[StrictStr] = Field(default=None, alias="createdAt")
- updated_at: Optional[StrictStr] = Field(default=None, alias="updatedAt")
- initialized_at: Optional[StrictStr] = Field(default=None, alias="initializedAt")
- id: Optional[StrictStr] = None
- duplicate_of: Optional[StrictStr] = Field(default=None, alias="duplicateOf")
- slug: Optional[StrictStr] = None
- name: Optional[StrictStr] = None
- summary: Optional[StrictStr] = None
- short_summary: Optional[StrictStr] = Field(default=None, alias="shortSummary")
+ Story cluster embedded on an article, without selected articles.
+ """
+
+ created_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ initialized_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story became initialized and visible, in ISO 8601 format.",
+ alias="initializedAt",
+ )
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique identifier for the story cluster."
+ )
+ duplicate_of: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier of the original story when this story is a duplicate.",
+ alias="duplicateOf",
+ )
+ slug: Optional[StrictStr] = Field(
+ default=None, description="URL-safe identifier for the story."
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Generated story title."
+ )
+ summary: Optional[StrictStr] = Field(
+ default=None, description="Generated story summary."
+ )
+ short_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Short generated story summary, when available.",
+ alias="shortSummary",
+ )
summary_references: Optional[List[StrictStr]] = Field(
- default=None, alias="summaryReferences"
+ default=None,
+ description="Article IDs supporting the story summary.",
+ alias="summaryReferences",
)
image_source: Optional[SourceHolder] = Field(default=None, alias="imageSource")
- image_url: Optional[StrictStr] = Field(default=None, alias="imageUrl")
- key_points: Optional[List[KeyPoint]] = Field(default=None, alias="keyPoints")
- questions: Optional[List[Question]] = None
- unique_sources: Optional[List[StrictStr]] = Field(
- default=None, alias="uniqueSources"
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Primary image URL selected for the story.",
+ alias="imageUrl",
)
- selected_articles: Optional[List[Article]] = Field(
- default=None, alias="selectedArticles"
+ key_points: Optional[List[KeyPoint]] = Field(
+ default=None,
+ description="Key facts and developments identified in the story.",
+ alias="keyPoints",
+ )
+ questions: Optional[List[Question]] = Field(
+ default=None,
+ description="Questions and answers generated from the story content.",
+ )
+ unique_sources: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Distinct publisher domains represented in the story.",
+ alias="uniqueSources",
)
sentiment: Optional[SentimentHolder] = None
- unique_count: Optional[StrictInt] = Field(default=None, alias="uniqueCount")
- reprint_count: Optional[StrictInt] = Field(default=None, alias="reprintCount")
- total_count: Optional[StrictInt] = Field(default=None, alias="totalCount")
- countries: Optional[List[RecordStatHolder]] = None
- top_countries: Optional[List[StrictStr]] = Field(default=None, alias="topCountries")
- topics: Optional[List[RecordStatHolder]] = None
- top_topics: Optional[List[TopicHolder]] = Field(default=None, alias="topTopics")
- categories: Optional[List[RecordStatHolder]] = None
+ unique_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of unique articles in the story.",
+ alias="uniqueCount",
+ )
+ reprint_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of reprinted articles in the story.",
+ alias="reprintCount",
+ )
+ total_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Total article count including reprints.",
+ alias="totalCount",
+ )
+ countries: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Countries associated with the story and their article counts.",
+ )
+ top_countries: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Countries most strongly associated with the story.",
+ alias="topCountries",
+ )
+ topics: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Topics associated with the story and their article counts.",
+ )
+ top_topics: Optional[List[TopicHolder]] = Field(
+ default=None,
+ description="Topics most strongly associated with the story.",
+ alias="topTopics",
+ )
+ categories: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Categories associated with the story and their article counts.",
+ )
top_categories: Optional[List[CategoryHolder]] = Field(
- default=None, alias="topCategories"
+ default=None,
+ description="Categories most strongly associated with the story.",
+ alias="topCategories",
+ )
+ taxonomies: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Google Content Categories associated with the story and their article counts.",
)
- taxonomies: Optional[List[RecordStatHolder]] = None
top_taxonomies: Optional[List[CategoryHolder]] = Field(
- default=None, alias="topTaxonomies"
+ default=None,
+ description="Google Content Categories most strongly associated with the story.",
+ alias="topTaxonomies",
+ )
+ people: Optional[List[PersonCount]] = Field(
+ default=None,
+ description="People associated with the story and their article counts.",
+ )
+ top_people: Optional[List[PersonHolder]] = Field(
+ default=None,
+ description="People most strongly associated with the story.",
+ alias="topPeople",
+ )
+ companies: Optional[List[CompanyCount]] = Field(
+ default=None,
+ description="Companies associated with the story and their article counts.",
)
- people: Optional[List[PersonCount]] = None
- top_people: Optional[List[PersonHolder]] = Field(default=None, alias="topPeople")
- companies: Optional[List[CompanyCount]] = None
top_companies: Optional[List[CompanyHolder]] = Field(
- default=None, alias="topCompanies"
+ default=None,
+ description="Companies most strongly associated with the story.",
+ alias="topCompanies",
+ )
+ locations: Optional[List[LocationCount]] = Field(
+ default=None,
+ description="Locations associated with the story and their article counts.",
)
- locations: Optional[List[LocationCount]] = None
top_locations: Optional[List[LocationHolder]] = Field(
- default=None, alias="topLocations"
+ default=None,
+ description="Locations most strongly associated with the story.",
+ alias="topLocations",
+ )
+ highlights: Optional[Dict[str, List[StrictStr]]] = Field(
+ default=None, description="Matched text fragments grouped by story field."
)
- highlights: Optional[Dict[str, List[StrictStr]]] = None
source_diversity: Optional[Union[StrictFloat, StrictInt]] = Field(
- default=None, alias="sourceDiversity"
+ default=None,
+ description="Ratio of unique publisher domains to total articles in the story.",
+ alias="sourceDiversity",
)
__properties: ClassVar[List[str]] = [
"createdAt",
@@ -111,7 +208,6 @@ class NewsCluster(BaseModel):
"keyPoints",
"questions",
"uniqueSources",
- "selectedArticles",
"sentiment",
"uniqueCount",
"reprintCount",
@@ -135,7 +231,8 @@ class NewsCluster(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -146,12 +243,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of NewsCluster from a JSON string"""
+ """Create an instance of ArticleNewsCluster from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -178,23 +274,18 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.key_points:
for _item_key_points in self.key_points:
- if _item_key_points:
- _items.append(_item_key_points.to_dict())
+ _items.append(
+ _item_key_points.to_dict() if _item_key_points is not None else None
+ )
_dict["keyPoints"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in questions (list)
_items = []
if self.questions:
for _item_questions in self.questions:
- if _item_questions:
- _items.append(_item_questions.to_dict())
+ _items.append(
+ _item_questions.to_dict() if _item_questions is not None else None
+ )
_dict["questions"] = _items
- # override the default output from pydantic by calling `to_dict()` of each item in selected_articles (list)
- _items = []
- if self.selected_articles:
- for _item_selected_articles in self.selected_articles:
- if _item_selected_articles:
- _items.append(_item_selected_articles.to_dict())
- _dict["selectedArticles"] = _items
# override the default output from pydantic by calling `to_dict()` of sentiment
if self.sentiment:
_dict["sentiment"] = self.sentiment.to_dict()
@@ -202,92 +293,113 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.countries:
for _item_countries in self.countries:
- if _item_countries:
- _items.append(_item_countries.to_dict())
+ _items.append(
+ _item_countries.to_dict() if _item_countries is not None else None
+ )
_dict["countries"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in topics (list)
_items = []
if self.topics:
for _item_topics in self.topics:
- if _item_topics:
- _items.append(_item_topics.to_dict())
+ _items.append(
+ _item_topics.to_dict() if _item_topics is not None else None
+ )
_dict["topics"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_topics (list)
_items = []
if self.top_topics:
for _item_top_topics in self.top_topics:
- if _item_top_topics:
- _items.append(_item_top_topics.to_dict())
+ _items.append(
+ _item_top_topics.to_dict() if _item_top_topics is not None else None
+ )
_dict["topTopics"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in categories (list)
_items = []
if self.categories:
for _item_categories in self.categories:
- if _item_categories:
- _items.append(_item_categories.to_dict())
+ _items.append(
+ _item_categories.to_dict() if _item_categories is not None else None
+ )
_dict["categories"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_categories (list)
_items = []
if self.top_categories:
for _item_top_categories in self.top_categories:
- if _item_top_categories:
- _items.append(_item_top_categories.to_dict())
+ _items.append(
+ _item_top_categories.to_dict()
+ if _item_top_categories is not None
+ else None
+ )
_dict["topCategories"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in taxonomies (list)
_items = []
if self.taxonomies:
for _item_taxonomies in self.taxonomies:
- if _item_taxonomies:
- _items.append(_item_taxonomies.to_dict())
+ _items.append(
+ _item_taxonomies.to_dict() if _item_taxonomies is not None else None
+ )
_dict["taxonomies"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_taxonomies (list)
_items = []
if self.top_taxonomies:
for _item_top_taxonomies in self.top_taxonomies:
- if _item_top_taxonomies:
- _items.append(_item_top_taxonomies.to_dict())
+ _items.append(
+ _item_top_taxonomies.to_dict()
+ if _item_top_taxonomies is not None
+ else None
+ )
_dict["topTaxonomies"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in people (list)
_items = []
if self.people:
for _item_people in self.people:
- if _item_people:
- _items.append(_item_people.to_dict())
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
_dict["people"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_people (list)
_items = []
if self.top_people:
for _item_top_people in self.top_people:
- if _item_top_people:
- _items.append(_item_top_people.to_dict())
+ _items.append(
+ _item_top_people.to_dict() if _item_top_people is not None else None
+ )
_dict["topPeople"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in companies (list)
_items = []
if self.companies:
for _item_companies in self.companies:
- if _item_companies:
- _items.append(_item_companies.to_dict())
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
_dict["companies"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_companies (list)
_items = []
if self.top_companies:
for _item_top_companies in self.top_companies:
- if _item_top_companies:
- _items.append(_item_top_companies.to_dict())
+ _items.append(
+ _item_top_companies.to_dict()
+ if _item_top_companies is not None
+ else None
+ )
_dict["topCompanies"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in locations (list)
_items = []
if self.locations:
for _item_locations in self.locations:
- if _item_locations:
- _items.append(_item_locations.to_dict())
+ _items.append(
+ _item_locations.to_dict() if _item_locations is not None else None
+ )
_dict["locations"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_locations (list)
_items = []
if self.top_locations:
for _item_top_locations in self.top_locations:
- if _item_top_locations:
- _items.append(_item_top_locations.to_dict())
+ _items.append(
+ _item_top_locations.to_dict()
+ if _item_top_locations is not None
+ else None
+ )
_dict["topLocations"] = _items
# set to None if created_at (nullable) is None
# and model_fields_set contains the field
@@ -367,14 +479,6 @@ def to_dict(self) -> Dict[str, Any]:
if self.unique_sources is None and "unique_sources" in self.model_fields_set:
_dict["uniqueSources"] = None
- # set to None if selected_articles (nullable) is None
- # and model_fields_set contains the field
- if (
- self.selected_articles is None
- and "selected_articles" in self.model_fields_set
- ):
- _dict["selectedArticles"] = None
-
# set to None if sentiment (nullable) is None
# and model_fields_set contains the field
if self.sentiment is None and "sentiment" in self.model_fields_set:
@@ -482,7 +586,7 @@ def to_dict(self) -> Dict[str, Any]:
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of NewsCluster from a dict"""
+ """Create an instance of ArticleNewsCluster from a dict"""
if obj is None:
return None
@@ -501,110 +605,87 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"summary": obj.get("summary"),
"shortSummary": obj.get("shortSummary"),
"summaryReferences": obj.get("summaryReferences"),
- "imageSource": (
- SourceHolder.from_dict(obj["imageSource"])
- if obj.get("imageSource") is not None
- else None
- ),
+ "imageSource": SourceHolder.from_dict(obj["imageSource"])
+ if obj.get("imageSource") is not None
+ else None,
"imageUrl": obj.get("imageUrl"),
- "keyPoints": (
- [KeyPoint.from_dict(_item) for _item in obj["keyPoints"]]
- if obj.get("keyPoints") is not None
- else None
- ),
- "questions": (
- [Question.from_dict(_item) for _item in obj["questions"]]
- if obj.get("questions") is not None
- else None
- ),
+ "keyPoints": [KeyPoint.from_dict(_item) for _item in obj["keyPoints"]]
+ if obj.get("keyPoints") is not None
+ else None,
+ "questions": [Question.from_dict(_item) for _item in obj["questions"]]
+ if obj.get("questions") is not None
+ else None,
"uniqueSources": obj.get("uniqueSources"),
- "selectedArticles": (
- [Article.from_dict(_item) for _item in obj["selectedArticles"]]
- if obj.get("selectedArticles") is not None
- else None
- ),
- "sentiment": (
- SentimentHolder.from_dict(obj["sentiment"])
- if obj.get("sentiment") is not None
- else None
- ),
+ "sentiment": SentimentHolder.from_dict(obj["sentiment"])
+ if obj.get("sentiment") is not None
+ else None,
"uniqueCount": obj.get("uniqueCount"),
"reprintCount": obj.get("reprintCount"),
"totalCount": obj.get("totalCount"),
- "countries": (
- [RecordStatHolder.from_dict(_item) for _item in obj["countries"]]
- if obj.get("countries") is not None
- else None
- ),
+ "countries": [
+ RecordStatHolder.from_dict(_item) for _item in obj["countries"]
+ ]
+ if obj.get("countries") is not None
+ else None,
"topCountries": obj.get("topCountries"),
- "topics": (
- [RecordStatHolder.from_dict(_item) for _item in obj["topics"]]
- if obj.get("topics") is not None
- else None
- ),
- "topTopics": (
- [TopicHolder.from_dict(_item) for _item in obj["topTopics"]]
- if obj.get("topTopics") is not None
- else None
- ),
- "categories": (
- [RecordStatHolder.from_dict(_item) for _item in obj["categories"]]
- if obj.get("categories") is not None
- else None
- ),
- "topCategories": (
- [CategoryHolder.from_dict(_item) for _item in obj["topCategories"]]
- if obj.get("topCategories") is not None
- else None
- ),
- "taxonomies": (
- [RecordStatHolder.from_dict(_item) for _item in obj["taxonomies"]]
- if obj.get("taxonomies") is not None
- else None
- ),
- "topTaxonomies": (
- [CategoryHolder.from_dict(_item) for _item in obj["topTaxonomies"]]
- if obj.get("topTaxonomies") is not None
- else None
- ),
- "people": (
- [PersonCount.from_dict(_item) for _item in obj["people"]]
- if obj.get("people") is not None
- else None
- ),
- "topPeople": (
- [PersonHolder.from_dict(_item) for _item in obj["topPeople"]]
- if obj.get("topPeople") is not None
- else None
- ),
- "companies": (
- [CompanyCount.from_dict(_item) for _item in obj["companies"]]
- if obj.get("companies") is not None
- else None
- ),
- "topCompanies": (
- [CompanyHolder.from_dict(_item) for _item in obj["topCompanies"]]
- if obj.get("topCompanies") is not None
- else None
- ),
- "locations": (
- [LocationCount.from_dict(_item) for _item in obj["locations"]]
- if obj.get("locations") is not None
- else None
- ),
- "topLocations": (
- [LocationHolder.from_dict(_item) for _item in obj["topLocations"]]
- if obj.get("topLocations") is not None
- else None
- ),
+ "topics": [RecordStatHolder.from_dict(_item) for _item in obj["topics"]]
+ if obj.get("topics") is not None
+ else None,
+ "topTopics": [
+ TopicHolder.from_dict(_item) for _item in obj["topTopics"]
+ ]
+ if obj.get("topTopics") is not None
+ else None,
+ "categories": [
+ RecordStatHolder.from_dict(_item) for _item in obj["categories"]
+ ]
+ if obj.get("categories") is not None
+ else None,
+ "topCategories": [
+ CategoryHolder.from_dict(_item) for _item in obj["topCategories"]
+ ]
+ if obj.get("topCategories") is not None
+ else None,
+ "taxonomies": [
+ RecordStatHolder.from_dict(_item) for _item in obj["taxonomies"]
+ ]
+ if obj.get("taxonomies") is not None
+ else None,
+ "topTaxonomies": [
+ CategoryHolder.from_dict(_item) for _item in obj["topTaxonomies"]
+ ]
+ if obj.get("topTaxonomies") is not None
+ else None,
+ "people": [PersonCount.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "topPeople": [
+ PersonHolder.from_dict(_item) for _item in obj["topPeople"]
+ ]
+ if obj.get("topPeople") is not None
+ else None,
+ "companies": [
+ CompanyCount.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "topCompanies": [
+ CompanyHolder.from_dict(_item) for _item in obj["topCompanies"]
+ ]
+ if obj.get("topCompanies") is not None
+ else None,
+ "locations": [
+ LocationCount.from_dict(_item) for _item in obj["locations"]
+ ]
+ if obj.get("locations") is not None
+ else None,
+ "topLocations": [
+ LocationHolder.from_dict(_item) for _item in obj["topLocations"]
+ ]
+ if obj.get("topLocations") is not None
+ else None,
"highlights": obj.get("highlights"),
"sourceDiversity": obj.get("sourceDiversity"),
}
)
return _obj
-
-
-from perigon.models.article import Article
-
-# TODO: Rewrite to not use raise_errors
-NewsCluster.model_rebuild(raise_errors=False)
diff --git a/perigon/models/article_search_filter.py b/src/perigon/models/article_search_filter.py
similarity index 90%
rename from perigon/models/article_search_filter.py
rename to src/perigon/models/article_search_filter.py
index f7983e5..3eccbd5 100644
--- a/perigon/models/article_search_filter.py
+++ b/src/perigon/models/article_search_filter.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.coordinate_filter import CoordinateFilter
@@ -29,132 +29,159 @@
class ArticleSearchFilter(BaseModel):
"""
Complex filter structure for article searches that supports nested logical operations (AND, OR, NOT) and multiple filtering criteria.
- """ # noqa: E501
+ """
article_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific article identifiers. Accepts either a single ID or an array of IDs. Returns only articles matching these IDs.",
alias="articleId",
+ json_schema_extra={"examples": [[""]]},
)
cluster_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific story identifiers. Accepts either a single ID or an array of IDs. Returns only articles belonging to these stories.",
alias="clusterId",
+ json_schema_extra={"examples": [[""]]},
)
source: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by specific publisher domains or subdomains. Accepts either a single domain or an array of domains. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_source: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles from specific publisher domains or subdomains. Accepts either a single domain or an array of domains. Multiple values create an AND-exclude filter.",
alias="excludeSource",
+ json_schema_extra={"examples": [[""]]},
)
source_group: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles using Perigon's curated publisher bundles (e.g., top100, top25tech). Accepts either a single source group or an array. Multiple values create an OR filter to include articles from any of the specified bundles.",
alias="sourceGroup",
+ json_schema_extra={"examples": [[""]]},
)
language: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by their language using ISO-639 two-letter codes in lowercase (e.g., en, es, fr). Accepts either a single language code or an array. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_language: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles in specific languages using ISO-639 two-letter codes in lowercase. Accepts either a single language code or an array. Multiple values create an AND-exclude filter.",
alias="excludeLanguage",
+ json_schema_extra={"examples": [[""]]},
)
label: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by editorial labels such as Opinion, Paid-news, Non-news, Fact Check, or Press Release. View our docs for an exhaustive list of labels. Accepts either a single label or an array. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_label: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles with specific editorial labels. Accepts either a single label or an array. Multiple values create an AND-exclude filter, removing all content with any of these labels.",
alias="excludeLabel",
+ json_schema_extra={"examples": [[""]]},
)
taxonomy: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by Google Content Categories. Must pass the full hierarchical path of the category. Accepts either a single path or an array. Example: taxonomy=/Finance/Banking/Other,/Finance/Investing/Funds. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
category: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by broad content categories such as Politics, Tech, Sports, Business, or Finance. Accepts either a single category or an array. Use none to find uncategorized articles. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
topic: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific topics such as Markets, Crime, Cryptocurrency, or College Sports. Accepts either a single topic or an array. Topics are more granular than categories, and articles can have multiple topics. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_topic: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles with specific topics. Accepts either a single topic or an array. Multiple values create an AND-exclude filter, removing all content with any of these topics.",
alias="excludeTopic",
+ json_schema_extra={"examples": [[""]]},
)
country: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by countries they mention using two-letter country codes in lowercase (e.g., us, gb, jp). Accepts either a single country code or an array. Multiple values create an OR filter. See documentation for supported country codes.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_country: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles from specific countries using two-letter country codes in lowercase. Accepts either a single country code or an array. Multiple values create an AND-exclude filter. See documentation for supported country codes.",
alias="excludeCountry",
+ json_schema_extra={"examples": [[""]]},
)
locations_country: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles where specified countries play a central role in the content, not just mentioned. Uses two-letter country codes in lowercase. Accepts either a single country code or an array. Multiple values create an OR filter. See documentation for supported country codes.",
alias="locationsCountry",
+ json_schema_extra={"examples": [[""]]},
)
exclude_locations_country: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles where specified countries play a central role in the content. Accepts either a single country code or an array. Multiple values create an AND-exclude filter, removing articles focused on any of these countries. See documentation for supported country codes.",
alias="excludeLocationsCountry",
+ json_schema_extra={"examples": [[""]]},
)
state: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles where specified states play a central role in the content. Accepts either a single state code or an array. Multiple values create an OR filter. Uses two-letter state codes in lowercase. See documentation for supported state codes.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_state: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles where specified states play a central role. Accepts either a single state code or an array. Multiple values create an AND-exclude filter, removing articles focused on any of these states. See documentation for supported state codes.",
alias="excludeState",
+ json_schema_extra={"examples": [[""]]},
)
county: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles that mention or are related to specific counties. Accepts either a single county name or an array. Multiple values create an OR filter. County names typically include the word 'County' (e.g., Los Angeles County).",
+ json_schema_extra={"examples": [[""]]},
)
exclude_county: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles that mention or are related to specific counties. Accepts either a single county name or an array. Multiple values create an AND-exclude filter. County names should match the format in article metadata (e.g., Los Angeles County, Cook County).",
alias="excludeCounty",
+ json_schema_extra={"examples": [[""]]},
)
city: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles that mention or are related to specific cities. Accepts either a single city name or an array. Multiple values create an OR filter.",
+ json_schema_extra={"examples": [[""]]},
)
exclude_city: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles that mention or are related to specific cities. Accepts either a single city name or an array. Multiple values create an AND-exclude filter.",
alias="excludeCity",
+ json_schema_extra={"examples": [[""]]},
)
source_country: Optional[List[StrictStr]] = Field(
default=None,
description="Filter for articles from publishers based in specific countries. Accepts either a single country code or an array. Uses two-letter country codes in lowercase (e.g., us, gb). See documentation for supported country codes.",
alias="sourceCountry",
+ json_schema_extra={"examples": [[""]]},
)
source_state: Optional[List[StrictStr]] = Field(
default=None,
description="Filter for articles from publishers based in specific states or regions. Accepts either a single state code or an array. Uses two-letter state codes in lowercase. See documentation for supported state codes.",
alias="sourceState",
+ json_schema_extra={"examples": [[""]]},
)
source_county: Optional[List[StrictStr]] = Field(
default=None,
description="Filter for articles from publishers based in specific counties. Accepts either a single county name or an array. Multiple values create an OR filter.",
alias="sourceCounty",
+ json_schema_extra={"examples": [[""]]},
)
source_city: Optional[List[StrictStr]] = Field(
default=None,
description="Filter for articles from publishers based in specific cities. Accepts either a single city name or an array. Multiple values create an OR filter.",
alias="sourceCity",
+ json_schema_extra={"examples": [[""]]},
)
coordinates: Optional[CoordinateFilter] = None
source_coordinates: Optional[CoordinateFilter] = Field(
@@ -164,71 +191,85 @@ class ArticleSearchFilter(BaseModel):
default=None,
description="Filter articles by company identifiers. Accepts either a single ID or an array. Multiple values create an OR filter. For a complete list of tracked companies and their IDs, refer to the /companies endpoint.",
alias="companyId",
+ json_schema_extra={"examples": [[""]]},
)
exclude_company_id: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles mentioning companies with specific identifiers. Accepts either a single ID or an array. Multiple values create an AND-exclude filter. For a complete list of tracked companies and their IDs, refer to the /companies endpoint.",
alias="excludeCompanyId",
+ json_schema_extra={"examples": [[""]]},
)
company_domain: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by company domains (e.g., apple.com). Accepts either a single domain or an array. Multiple values create an OR filter. For a complete list of tracked companies and their domains, refer to the /companies endpoint.",
alias="companyDomain",
+ json_schema_extra={"examples": [[""]]},
)
exclude_company_domain: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles related to companies with specific domains. Accepts either a single domain or an array. Multiple values create an AND-exclude filter. For a complete list of tracked companies and their domains, refer to the /companies endpoint.",
alias="excludeCompanyDomain",
+ json_schema_extra={"examples": [[""]]},
)
company_symbol: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by company stock symbols (e.g., AAPL, MSFT). Accepts either a single symbol or an array. Multiple values create an OR filter. For a complete list of tracked companies and their symbols, refer to the /companies endpoint.",
alias="companySymbol",
+ json_schema_extra={"examples": [[""]]},
)
exclude_company_symbol: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles related to companies with specific stock symbols. Accepts either a single symbol or an array. Multiple values create an AND-exclude filter. For a complete list of tracked companies and their symbols, refer to the /companies endpoint.",
alias="excludeCompanySymbol",
+ json_schema_extra={"examples": [[""]]},
)
company_name: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by company name mentions. Accepts either a single name or an array. Performs exact matching on company names. Multiple values create an OR filter. For a complete list of tracked companies and their names, refer to the /companies endpoint.",
alias="companyName",
+ json_schema_extra={"examples": [[""]]},
)
person_wikidata_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by Wikidata IDs of mentioned people. Accepts either a single ID or an array. Multiple values create an OR filter. For a complete list of tracked individuals and their Wikidata IDs, refer to the /people endpoint.",
alias="personWikidataId",
+ json_schema_extra={"examples": [[""]]},
)
exclude_person_wikidata_id: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles mentioning people with specific Wikidata IDs. Accepts either a single ID or an array. Multiple values create an AND-exclude filter. For a complete list of tracked individuals and their Wikidata IDs, refer to the /people endpoint.",
alias="excludePersonWikidataId",
+ json_schema_extra={"examples": [[""]]},
)
person_name: Optional[List[StrictStr]] = Field(
default=None,
description="Filter articles by exact person name matches. Accepts either a single name or an array. Does not support Boolean operators or wildcards. Multiple values create an OR filter. For a complete list of tracked individuals and their names, refer to the /people endpoint.",
alias="personName",
+ json_schema_extra={"examples": [[""]]},
)
exclude_person_name: Optional[List[StrictStr]] = Field(
default=None,
description="Exclude articles mentioning specific people by name. Accepts either a single name or an array. Multiple values create an AND-exclude filter. For a complete list of tracked individuals and their names, refer to the /people endpoint.",
alias="excludePersonName",
+ json_schema_extra={"examples": [[""]]},
)
var_and: Optional[List[ArticleSearchFilter]] = Field(
default=None,
description="Adds additional AND filter objects. These objects must be of the same type as the original filter object and will be combined with the existing filter using the AND logical operator.",
alias="AND",
+ json_schema_extra={"examples": [[{}]]},
)
var_or: Optional[List[ArticleSearchFilter]] = Field(
default=None,
description="Adds additional OR filter objects. These objects must be of the same type as the original filter object and will be combined with the existing filter using the OR logical operator.",
alias="OR",
+ json_schema_extra={"examples": [[{}]]},
)
var_not: Optional[List[ArticleSearchFilter]] = Field(
default=None,
description="A filter object for logical NOT operations",
alias="NOT",
+ json_schema_extra={"examples": [[{}]]},
)
__properties: ClassVar[List[str]] = [
"articleId",
@@ -277,7 +318,8 @@ class ArticleSearchFilter(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -288,8 +330,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -323,22 +364,25 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.var_and:
for _item_var_and in self.var_and:
- if _item_var_and:
- _items.append(_item_var_and.to_dict())
+ _items.append(
+ _item_var_and.to_dict() if _item_var_and is not None else None
+ )
_dict["AND"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in var_or (list)
_items = []
if self.var_or:
for _item_var_or in self.var_or:
- if _item_var_or:
- _items.append(_item_var_or.to_dict())
+ _items.append(
+ _item_var_or.to_dict() if _item_var_or is not None else None
+ )
_dict["OR"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in var_not (list)
_items = []
if self.var_not:
for _item_var_not in self.var_not:
- if _item_var_not:
- _items.append(_item_var_not.to_dict())
+ _items.append(
+ _item_var_not.to_dict() if _item_var_not is not None else None
+ )
_dict["NOT"] = _items
# set to None if article_id (nullable) is None
# and model_fields_set contains the field
@@ -625,16 +669,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"sourceState": obj.get("sourceState"),
"sourceCounty": obj.get("sourceCounty"),
"sourceCity": obj.get("sourceCity"),
- "coordinates": (
- CoordinateFilter.from_dict(obj["coordinates"])
- if obj.get("coordinates") is not None
- else None
- ),
- "sourceCoordinates": (
- CoordinateFilter.from_dict(obj["sourceCoordinates"])
- if obj.get("sourceCoordinates") is not None
- else None
- ),
+ "coordinates": CoordinateFilter.from_dict(obj["coordinates"])
+ if obj.get("coordinates") is not None
+ else None,
+ "sourceCoordinates": CoordinateFilter.from_dict(
+ obj["sourceCoordinates"]
+ )
+ if obj.get("sourceCoordinates") is not None
+ else None,
"companyId": obj.get("companyId"),
"excludeCompanyId": obj.get("excludeCompanyId"),
"companyDomain": obj.get("companyDomain"),
@@ -646,21 +688,15 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"excludePersonWikidataId": obj.get("excludePersonWikidataId"),
"personName": obj.get("personName"),
"excludePersonName": obj.get("excludePersonName"),
- "AND": (
- [ArticleSearchFilter.from_dict(_item) for _item in obj["AND"]]
- if obj.get("AND") is not None
- else None
- ),
- "OR": (
- [ArticleSearchFilter.from_dict(_item) for _item in obj["OR"]]
- if obj.get("OR") is not None
- else None
- ),
- "NOT": (
- [ArticleSearchFilter.from_dict(_item) for _item in obj["NOT"]]
- if obj.get("NOT") is not None
- else None
- ),
+ "AND": [ArticleSearchFilter.from_dict(_item) for _item in obj["AND"]]
+ if obj.get("AND") is not None
+ else None,
+ "OR": [ArticleSearchFilter.from_dict(_item) for _item in obj["OR"]]
+ if obj.get("OR") is not None
+ else None,
+ "NOT": [ArticleSearchFilter.from_dict(_item) for _item in obj["NOT"]]
+ if obj.get("NOT") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/article_search_params.py b/src/perigon/models/article_search_params.py
similarity index 87%
rename from perigon/models/article_search_params.py
rename to src/perigon/models/article_search_params.py
index dd91635..eff7135 100644
--- a/perigon/models/article_search_params.py
+++ b/src/perigon/models/article_search_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -22,6 +21,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictBool
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
from perigon.models.article_search_filter import ArticleSearchFilter
@@ -30,21 +30,24 @@
class ArticleSearchParams(BaseModel):
"""
ArticleSearchParams
- """ # noqa: E501
+ """
prompt: Annotated[str, Field(min_length=0, strict=True, max_length=1024)] = Field(
- description="Natural language query to search the news articles database"
+ description="Natural language query to search the news articles database",
+ json_schema_extra={"examples": ["Enter your prompt"]},
)
filter: Optional[ArticleSearchFilter] = None
pub_date_from: Optional[datetime] = Field(
default=None,
description="'pubDateFrom' filter, will search articles published after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Date time in ISO format, ie. 2024-01-01T00:00:00 - Default: Only articles with a pubDate within the last 30 days of the request",
alias="pubDateFrom",
+ json_schema_extra={"examples": ["2024-01-01T00:00:00Z"]},
)
pub_date_to: Optional[datetime] = Field(
default=None,
description="'pubDateTo' filter, will search articles published before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Date time in ISO format, ie. 2024-01-01T00:00:00",
alias="pubDateTo",
+ json_schema_extra={"examples": ["2024-12-31T23:59:59Z"]},
)
show_reprints: Optional[StrictBool] = Field(
default=True,
@@ -68,7 +71,8 @@ class ArticleSearchParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -79,8 +83,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -151,20 +154,16 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"prompt": obj.get("prompt"),
- "filter": (
- ArticleSearchFilter.from_dict(obj["filter"])
- if obj.get("filter") is not None
- else None
- ),
+ "filter": ArticleSearchFilter.from_dict(obj["filter"])
+ if obj.get("filter") is not None
+ else None,
"pubDateFrom": obj.get("pubDateFrom"),
"pubDateTo": obj.get("pubDateTo"),
- "showReprints": (
- obj.get("showReprints")
- if obj.get("showReprints") is not None
- else True
- ),
- "size": obj.get("size") if obj.get("size") is not None else 10,
- "page": obj.get("page") if obj.get("page") is not None else 0,
+ "showReprints": obj.get("showReprints")
+ if "showReprints" in obj
+ else True,
+ "size": obj.get("size") if "size" in obj else 10,
+ "page": obj.get("page") if "page" in obj else 0,
}
)
return _obj
diff --git a/perigon/models/articles_vector_search_result.py b/src/perigon/models/articles_vector_search_result.py
similarity index 84%
rename from perigon/models/articles_vector_search_result.py
rename to src/perigon/models/articles_vector_search_result.py
index 434af66..ff91db1 100644
--- a/perigon/models/articles_vector_search_result.py
+++ b/src/perigon/models/articles_vector_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.scored_data_article import ScoredDataArticle
@@ -29,14 +29,15 @@
class ArticlesVectorSearchResult(BaseModel):
"""
Articles vector search result
- """ # noqa: E501
+ """
status: StrictInt
results: List[ScoredDataArticle]
__properties: ClassVar[List[str]] = ["status", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,8 +48,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -76,8 +76,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -93,11 +94,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"status": obj.get("status"),
- "results": (
- [ScoredDataArticle.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [
+ ScoredDataArticle.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/category_holder.py b/src/perigon/models/category_holder.py
similarity index 85%
rename from perigon/models/category_holder.py
rename to src/perigon/models/category_holder.py
index 05058cd..592b94f 100644
--- a/perigon/models/category_holder.py
+++ b/src/perigon/models/category_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,20 +19,24 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class CategoryHolder(BaseModel):
"""
- CategoryHolder
- """ # noqa: E501
+ Google Content Categories most strongly associated with the story.
+ """
- name: Optional[StrictStr] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Content category assigned to the article."
+ )
__properties: ClassVar[List[str]] = ["name"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -44,8 +47,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/category_with_score_holder.py b/src/perigon/models/category_with_score_holder.py
similarity index 82%
rename from perigon/models/category_with_score_holder.py
rename to src/perigon/models/category_with_score_holder.py
index ceb2585..f8badf7 100644
--- a/perigon/models/category_with_score_holder.py
+++ b/src/perigon/models/category_with_score_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class CategoryWithScoreHolder(BaseModel):
"""
- CategoryWithScoreHolder
- """ # noqa: E501
+ Google Content Categories assigned to the article with confidence scores.
+ """
- name: Optional[StrictStr] = None
- score: Optional[Union[StrictFloat, StrictInt]] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Full taxonomy category path."
+ )
+ score: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Confidence score for the category assignment."
+ )
__properties: ClassVar[List[str]] = ["name", "score"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/sort_by.py b/src/perigon/models/clusters_list_sort_by.py
similarity index 86%
rename from perigon/models/sort_by.py
rename to src/perigon/models/clusters_list_sort_by.py
index ceaeaf8..4d95d7c 100644
--- a/perigon/models/sort_by.py
+++ b/src/perigon/models/clusters_list_sort_by.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,9 +20,9 @@
from typing_extensions import Self
-class SortBy(str, Enum):
+class ClustersListSortBy(str, Enum):
"""
- SortBy
+ ClustersListSortBy
"""
"""
@@ -37,5 +36,5 @@ class SortBy(str, Enum):
@classmethod
def from_json(cls, json_str: str) -> Self:
- """Create an instance of SortBy from a JSON string"""
+ """Create an instance of ClustersListSortBy from a JSON string"""
return cls(json.loads(json_str))
diff --git a/perigon/models/company.py b/src/perigon/models/company.py
similarity index 95%
rename from perigon/models/company.py
rename to src/perigon/models/company.py
index 12876bb..3d015d5 100644
--- a/perigon/models/company.py
+++ b/src/perigon/models/company.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.symbol_holder import SymbolHolder
@@ -30,7 +30,7 @@
class Company(BaseModel):
"""
Company
- """ # noqa: E501
+ """
id: Optional[StrictStr] = None
name: Optional[StrictStr] = None
@@ -108,7 +108,8 @@ class Company(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -119,8 +120,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -148,8 +148,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.symbols:
for _item_symbols in self.symbols:
- if _item_symbols:
- _items.append(_item_symbols.to_dict())
+ _items.append(
+ _item_symbols.to_dict() if _item_symbols is not None else None
+ )
_dict["symbols"] = _items
# override the default output from pydantic by calling `to_dict()` of web_resources
if self.web_resources:
@@ -365,11 +366,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"isActivelyTrading": obj.get("isActivelyTrading"),
"isFund": obj.get("isFund"),
"isAdr": obj.get("isAdr"),
- "symbols": (
- [SymbolHolder.from_dict(_item) for _item in obj["symbols"]]
- if obj.get("symbols") is not None
- else None
- ),
+ "symbols": [SymbolHolder.from_dict(_item) for _item in obj["symbols"]]
+ if obj.get("symbols") is not None
+ else None,
"naics": obj.get("naics"),
"sic": obj.get("sic"),
"cusip": obj.get("cusip"),
@@ -377,11 +376,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"isin": obj.get("isin"),
"yearFounded": obj.get("yearFounded"),
"revenue": obj.get("revenue"),
- "webResources": (
- WebResources.from_dict(obj["webResources"])
- if obj.get("webResources") is not None
- else None
- ),
+ "webResources": WebResources.from_dict(obj["webResources"])
+ if obj.get("webResources") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/company_count.py b/src/perigon/models/company_count.py
similarity index 79%
rename from perigon/models/company_count.py
rename to src/perigon/models/company_count.py
index 15d701e..b545451 100644
--- a/perigon/models/company_count.py
+++ b/src/perigon/models/company_count.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,24 +19,34 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class CompanyCount(BaseModel):
"""
- CompanyCount
- """ # noqa: E501
-
- id: Optional[StrictStr] = None
- name: Optional[StrictStr] = None
- domains: Optional[List[StrictStr]] = None
- symbols: Optional[List[StrictStr]] = None
- count: Optional[StrictInt] = None
+ Companies associated with the story and their article counts.
+ """
+
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique Perigon identifier for the company."
+ )
+ name: Optional[StrictStr] = Field(default=None, description="Company name.")
+ domains: Optional[List[StrictStr]] = Field(
+ default=None, description="Domains associated with the company."
+ )
+ symbols: Optional[List[StrictStr]] = Field(
+ default=None, description="Stock symbols associated with the company."
+ )
+ count: Optional[StrictInt] = Field(
+ default=None, description="Number of story articles mentioning the company."
+ )
__properties: ClassVar[List[str]] = ["id", "name", "domains", "symbols", "count"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +57,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/company_holder.py b/src/perigon/models/company_holder.py
similarity index 81%
rename from perigon/models/company_holder.py
rename to src/perigon/models/company_holder.py
index b819dac..b99bd9e 100644
--- a/perigon/models/company_holder.py
+++ b/src/perigon/models/company_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,23 +19,31 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class CompanyHolder(BaseModel):
"""
- CompanyHolder
- """ # noqa: E501
+ Companies most strongly associated with the story.
+ """
- id: Optional[StrictStr] = None
- name: Optional[StrictStr] = None
- domains: Optional[List[StrictStr]] = None
- symbols: Optional[List[StrictStr]] = None
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique Perigon identifier for the company."
+ )
+ name: Optional[StrictStr] = Field(default=None, description="Company name.")
+ domains: Optional[List[StrictStr]] = Field(
+ default=None, description="Domains associated with the company."
+ )
+ symbols: Optional[List[StrictStr]] = Field(
+ default=None, description="Stock symbols associated with the company."
+ )
__properties: ClassVar[List[str]] = ["id", "name", "domains", "symbols"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,8 +54,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/company_search_result.py b/src/perigon/models/company_search_result.py
similarity index 85%
rename from perigon/models/company_search_result.py
rename to src/perigon/models/company_search_result.py
index a93f151..4cf6945 100644
--- a/perigon/models/company_search_result.py
+++ b/src/perigon/models/company_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.company import Company
@@ -29,7 +29,7 @@
class CompanySearchResult(BaseModel):
"""
Company search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class CompanySearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [Company.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [Company.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/src/perigon/models/complex_all_endpoint_query.py b/src/perigon/models/complex_all_endpoint_query.py
new file mode 100644
index 0000000..4ba665f
--- /dev/null
+++ b/src/perigon/models/complex_all_endpoint_query.py
@@ -0,0 +1,1239 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set, Union
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictFloat,
+ StrictInt,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class ComplexAllEndpointQuery(BaseModel):
+ """
+ Article search filters that determine which content the monitor evaluates.
+ """
+
+ q: Optional[StrictStr] = Field(
+ default=None,
+ description="Search article title, description, and content using keywords or Boolean syntax.",
+ )
+ url: Optional[StrictStr] = Field(
+ default=None, description="Filter for an exact article URL."
+ )
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles with this image URL.",
+ alias="imageUrl",
+ )
+ title: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles containing this text in the title.",
+ )
+ content: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles containing this text in the body content.",
+ )
+ desc: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles containing this text in the description.",
+ )
+ article_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by Perigon article IDs. Multiple values are combined with OR.",
+ alias="articleId",
+ )
+ cluster_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by Perigon story cluster IDs. Multiple values are combined with OR.",
+ alias="clusterId",
+ )
+ reprint_group_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Return the original article and known reprints in a reprint group. Must be a 32-character group ID.",
+ alias="reprintGroupId",
+ )
+ journalist_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by journalist IDs. Each ID must be exactly 32 characters.",
+ alias="journalistId",
+ )
+ medium: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by medium type, such as Article or Video. Multiple values are combined with OR.",
+ )
+ source_group: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by Perigon source groups. Multiple values are combined with OR.",
+ alias="sourceGroup",
+ )
+ exclude_source_group: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles from Perigon source groups. Multiple values exclude matches from any listed group.",
+ alias="excludeSourceGroup",
+ )
+ source: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by publisher domains or subdomains. Supports * and ? wildcards.",
+ )
+ exclude_source: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude publisher domains or subdomains. Supports * and ? wildcards.",
+ alias="excludeSource",
+ )
+ country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by article country codes. Use two-character codes such as us or gb; multiple values are combined with OR.",
+ )
+ exclude_country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles with the specified country codes. Use two-character codes such as us or gb.",
+ alias="excludeCountry",
+ )
+ language: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by ISO 639 two-letter language codes, such as en or es.",
+ )
+ exclude_language: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles in the specified ISO 639 two-letter languages.",
+ alias="excludeLanguage",
+ )
+ label: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by editorial labels, such as Opinion, Paid-news, or Press Release.",
+ )
+ exclude_label: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles with any of the specified editorial labels.",
+ alias="excludeLabel",
+ )
+ byline: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by exact author bylines. Multiple values are combined with OR.",
+ )
+ byline_q: Optional[StrictStr] = Field(
+ default=None,
+ description="Search within article author bylines.",
+ alias="bylineQ",
+ )
+ summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles containing this text in the summary.",
+ )
+ watchlist: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles mentioning entities from the specified watchlists.",
+ )
+ exclude_watchlist: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles mentioning entities from the specified watchlists.",
+ alias="excludeWatchlist",
+ )
+ exclude_journalist_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles written by journalists with these IDs.",
+ alias="excludeJournalistId",
+ )
+ exclude_source_city: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles published by sources in the specified cities.",
+ alias="excludeSourceCity",
+ )
+ exclude_source_state: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles published by sources in the specified states.",
+ alias="excludeSourceState",
+ )
+ lat: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Latitude of the location used to filter places. Must be between -90 and 90.",
+ )
+ lon: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Longitude of the location used to filter places. Must be between -180 and 180.",
+ )
+ max_distance: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Maximum distance in kilometers from lat and lon. Must be between 1 and 300.",
+ alias="maxDistance",
+ )
+ source_lat: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Latitude used to find nearby publishers. Must be between -90 and 90.",
+ alias="sourceLat",
+ )
+ source_lon: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Longitude used to find nearby publishers. Must be between -180 and 180.",
+ alias="sourceLon",
+ )
+ source_max_distance: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Maximum distance in kilometers from sourceLat and sourceLon. Must be between 1 and 300.",
+ alias="sourceMaxDistance",
+ )
+ min_word_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Filter for articles with at least this many words.",
+ alias="minWordCount",
+ )
+ max_word_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Filter for articles with no more than this many words.",
+ alias="maxWordCount",
+ )
+ event_type: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by detected event types. Multiple values are combined with OR.",
+ alias="eventType",
+ )
+ extractor_name: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by extractor names that processed the article.",
+ alias="extractorName",
+ )
+ exclude_extractor_name: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles processed by the specified extractor names.",
+ alias="excludeExtractorName",
+ )
+ exclude_source_country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles published by sources in the specified countries. Use two-character country codes such as us or gb.",
+ alias="excludeSourceCountry",
+ )
+ exclude_source_county: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles published by sources in the specified counties.",
+ alias="excludeSourceCounty",
+ )
+ author: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by exact author names. Multiple values are combined with OR.",
+ )
+ exclude_author: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles written by any of the specified authors.",
+ alias="excludeAuthor",
+ )
+ topic: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by article topics. Multiple values are combined with OR.",
+ )
+ exclude_topic: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles assigned any of the specified topics.",
+ alias="excludeTopic",
+ )
+ category: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by article categories. Multiple values are combined with OR; use none for uncategorized articles.",
+ )
+ taxonomy: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by full Google Content Category paths. Multiple values are combined with OR.",
+ )
+ prefix_taxonomy: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter by a Google Content Category path prefix, such as /Finance.",
+ alias="prefixTaxonomy",
+ )
+ company_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by Perigon company IDs. Multiple values are combined with OR.",
+ alias="companyId",
+ )
+ company_name: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter by an exact company name mention.",
+ alias="companyName",
+ )
+ company_domain: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by company domains, such as apple.com.",
+ alias="companyDomain",
+ )
+ company_symbol: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by company stock symbols.",
+ alias="companySymbol",
+ )
+ exclude_company_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles mentioning companies with these Perigon IDs.",
+ alias="excludeCompanyId",
+ )
+ exclude_company_domain: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles related to companies with these domains.",
+ alias="excludeCompanyDomain",
+ )
+ exclude_company_symbol: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles related to companies with these stock symbols.",
+ alias="excludeCompanySymbol",
+ )
+ person_wikidata_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by Wikidata IDs of people mentioned in articles.",
+ alias="personWikidataId",
+ )
+ person_name: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by exact person name mentions. Multiple values are combined with OR.",
+ alias="personName",
+ )
+ exclude_person_wikidata_id: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles mentioning people with these Wikidata IDs.",
+ alias="excludePersonWikidataId",
+ )
+ exclude_person_name: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles mentioning any of the specified people by name.",
+ alias="excludePersonName",
+ )
+ link_to: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter for articles containing links that match this URL pattern.",
+ alias="linkTo",
+ )
+ locations_country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles primarily associated with the specified countries.",
+ alias="locationsCountry",
+ )
+ state: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles primarily associated with the specified states.",
+ )
+ city: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles primarily associated with the specified cities.",
+ )
+ area: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles primarily associated with the specified areas, such as neighborhoods or districts.",
+ )
+ county: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles primarily associated with the specified counties.",
+ )
+ source_country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles published by sources in these countries. Use two-character country codes such as us or gb.",
+ alias="sourceCountry",
+ )
+ source_state: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles published by sources in the specified states.",
+ alias="sourceState",
+ )
+ source_county: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles published by sources in the specified counties.",
+ alias="sourceCounty",
+ )
+ source_city: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter for articles published by sources in the specified cities.",
+ alias="sourceCity",
+ )
+ exclude_locations_country: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles primarily associated with any of the specified countries.",
+ alias="excludeLocationsCountry",
+ )
+ exclude_state: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles primarily associated with any of the specified states.",
+ alias="excludeState",
+ )
+ exclude_county: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles primarily associated with any of the specified counties.",
+ alias="excludeCounty",
+ )
+ exclude_city: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles primarily associated with any of the specified cities.",
+ alias="excludeCity",
+ )
+ exclude_area: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles primarily associated with any of the specified areas.",
+ alias="excludeArea",
+ )
+ location: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by structured location attributes using key:value pairs separated by ::, such as city:New York::state:NY.",
+ )
+ source_location: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Filter by structured publisher location attributes.",
+ alias="sourceLocation",
+ )
+ exclude_category: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles assigned any of the specified categories.",
+ alias="excludeCategory",
+ )
+ exclude_taxonomy: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Exclude articles assigned any of the specified Google Content Categories.",
+ alias="excludeTaxonomy",
+ )
+ positive_sentiment_from: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Minimum positive sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="positiveSentimentFrom",
+ )
+ positive_sentiment_to: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Maximum positive sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="positiveSentimentTo",
+ )
+ negative_sentiment_from: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Minimum negative sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="negativeSentimentFrom",
+ )
+ negative_sentiment_to: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Maximum negative sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="negativeSentimentTo",
+ )
+ neutral_sentiment_from: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Minimum neutral sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="neutralSentimentFrom",
+ )
+ neutral_sentiment_to: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Maximum neutral sentiment score, inclusive. Accepts values from 0 to 1.",
+ alias="neutralSentimentTo",
+ )
+ search_translation: Optional[StrictBool] = Field(
+ default=None,
+ description="Search translated title, description, and content for non-English articles. Defaults to false.",
+ alias="searchTranslation",
+ )
+ paywall: Optional[StrictBool] = Field(
+ default=None, description="Filter by whether the publisher has a paywall."
+ )
+ type: Optional[StrictStr] = Field(
+ default=None,
+ description="Filter by geographic content scope. Accepts all, local, or world.",
+ )
+ qfield: Optional[List[StrictStr]] = None
+ __properties: ClassVar[List[str]] = [
+ "q",
+ "url",
+ "imageUrl",
+ "title",
+ "content",
+ "desc",
+ "articleId",
+ "clusterId",
+ "reprintGroupId",
+ "journalistId",
+ "medium",
+ "sourceGroup",
+ "excludeSourceGroup",
+ "source",
+ "excludeSource",
+ "country",
+ "excludeCountry",
+ "language",
+ "excludeLanguage",
+ "label",
+ "excludeLabel",
+ "byline",
+ "bylineQ",
+ "summary",
+ "watchlist",
+ "excludeWatchlist",
+ "excludeJournalistId",
+ "excludeSourceCity",
+ "excludeSourceState",
+ "lat",
+ "lon",
+ "maxDistance",
+ "sourceLat",
+ "sourceLon",
+ "sourceMaxDistance",
+ "minWordCount",
+ "maxWordCount",
+ "eventType",
+ "extractorName",
+ "excludeExtractorName",
+ "excludeSourceCountry",
+ "excludeSourceCounty",
+ "author",
+ "excludeAuthor",
+ "topic",
+ "excludeTopic",
+ "category",
+ "taxonomy",
+ "prefixTaxonomy",
+ "companyId",
+ "companyName",
+ "companyDomain",
+ "companySymbol",
+ "excludeCompanyId",
+ "excludeCompanyDomain",
+ "excludeCompanySymbol",
+ "personWikidataId",
+ "personName",
+ "excludePersonWikidataId",
+ "excludePersonName",
+ "linkTo",
+ "locationsCountry",
+ "state",
+ "city",
+ "area",
+ "county",
+ "sourceCountry",
+ "sourceState",
+ "sourceCounty",
+ "sourceCity",
+ "excludeLocationsCountry",
+ "excludeState",
+ "excludeCounty",
+ "excludeCity",
+ "excludeArea",
+ "location",
+ "sourceLocation",
+ "excludeCategory",
+ "excludeTaxonomy",
+ "positiveSentimentFrom",
+ "positiveSentimentTo",
+ "negativeSentimentFrom",
+ "negativeSentimentTo",
+ "neutralSentimentFrom",
+ "neutralSentimentTo",
+ "searchTranslation",
+ "paywall",
+ "type",
+ "qfield",
+ ]
+
+ @field_validator("type")
+ def type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["ALL", "LOCAL", "WORLD"]):
+ raise ValueError("must be one of enum values ('ALL', 'LOCAL', 'WORLD')")
+ return value
+
+ @field_validator("qfield")
+ def qfield_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ for i in value:
+ if i not in set(["TITLE", "DESCRIPTION", "CONTENT", "SUMMARY"]):
+ raise ValueError(
+ "each list item must be one of ('TITLE', 'DESCRIPTION', 'CONTENT', 'SUMMARY')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ComplexAllEndpointQuery from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if q (nullable) is None
+ # and model_fields_set contains the field
+ if self.q is None and "q" in self.model_fields_set:
+ _dict["q"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if image_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.image_url is None and "image_url" in self.model_fields_set:
+ _dict["imageUrl"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if content (nullable) is None
+ # and model_fields_set contains the field
+ if self.content is None and "content" in self.model_fields_set:
+ _dict["content"] = None
+
+ # set to None if desc (nullable) is None
+ # and model_fields_set contains the field
+ if self.desc is None and "desc" in self.model_fields_set:
+ _dict["desc"] = None
+
+ # set to None if article_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.article_id is None and "article_id" in self.model_fields_set:
+ _dict["articleId"] = None
+
+ # set to None if cluster_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.cluster_id is None and "cluster_id" in self.model_fields_set:
+ _dict["clusterId"] = None
+
+ # set to None if reprint_group_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.reprint_group_id is None
+ and "reprint_group_id" in self.model_fields_set
+ ):
+ _dict["reprintGroupId"] = None
+
+ # set to None if journalist_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.journalist_id is None and "journalist_id" in self.model_fields_set:
+ _dict["journalistId"] = None
+
+ # set to None if medium (nullable) is None
+ # and model_fields_set contains the field
+ if self.medium is None and "medium" in self.model_fields_set:
+ _dict["medium"] = None
+
+ # set to None if source_group (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_group is None and "source_group" in self.model_fields_set:
+ _dict["sourceGroup"] = None
+
+ # set to None if exclude_source_group (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_source_group is None
+ and "exclude_source_group" in self.model_fields_set
+ ):
+ _dict["excludeSourceGroup"] = None
+
+ # set to None if source (nullable) is None
+ # and model_fields_set contains the field
+ if self.source is None and "source" in self.model_fields_set:
+ _dict["source"] = None
+
+ # set to None if exclude_source (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_source is None and "exclude_source" in self.model_fields_set:
+ _dict["excludeSource"] = None
+
+ # set to None if country (nullable) is None
+ # and model_fields_set contains the field
+ if self.country is None and "country" in self.model_fields_set:
+ _dict["country"] = None
+
+ # set to None if exclude_country (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_country is None and "exclude_country" in self.model_fields_set:
+ _dict["excludeCountry"] = None
+
+ # set to None if language (nullable) is None
+ # and model_fields_set contains the field
+ if self.language is None and "language" in self.model_fields_set:
+ _dict["language"] = None
+
+ # set to None if exclude_language (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_language is None
+ and "exclude_language" in self.model_fields_set
+ ):
+ _dict["excludeLanguage"] = None
+
+ # set to None if label (nullable) is None
+ # and model_fields_set contains the field
+ if self.label is None and "label" in self.model_fields_set:
+ _dict["label"] = None
+
+ # set to None if exclude_label (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_label is None and "exclude_label" in self.model_fields_set:
+ _dict["excludeLabel"] = None
+
+ # set to None if byline (nullable) is None
+ # and model_fields_set contains the field
+ if self.byline is None and "byline" in self.model_fields_set:
+ _dict["byline"] = None
+
+ # set to None if byline_q (nullable) is None
+ # and model_fields_set contains the field
+ if self.byline_q is None and "byline_q" in self.model_fields_set:
+ _dict["bylineQ"] = None
+
+ # set to None if summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.summary is None and "summary" in self.model_fields_set:
+ _dict["summary"] = None
+
+ # set to None if watchlist (nullable) is None
+ # and model_fields_set contains the field
+ if self.watchlist is None and "watchlist" in self.model_fields_set:
+ _dict["watchlist"] = None
+
+ # set to None if exclude_watchlist (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_watchlist is None
+ and "exclude_watchlist" in self.model_fields_set
+ ):
+ _dict["excludeWatchlist"] = None
+
+ # set to None if exclude_journalist_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_journalist_id is None
+ and "exclude_journalist_id" in self.model_fields_set
+ ):
+ _dict["excludeJournalistId"] = None
+
+ # set to None if exclude_source_city (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_source_city is None
+ and "exclude_source_city" in self.model_fields_set
+ ):
+ _dict["excludeSourceCity"] = None
+
+ # set to None if exclude_source_state (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_source_state is None
+ and "exclude_source_state" in self.model_fields_set
+ ):
+ _dict["excludeSourceState"] = None
+
+ # set to None if lat (nullable) is None
+ # and model_fields_set contains the field
+ if self.lat is None and "lat" in self.model_fields_set:
+ _dict["lat"] = None
+
+ # set to None if lon (nullable) is None
+ # and model_fields_set contains the field
+ if self.lon is None and "lon" in self.model_fields_set:
+ _dict["lon"] = None
+
+ # set to None if max_distance (nullable) is None
+ # and model_fields_set contains the field
+ if self.max_distance is None and "max_distance" in self.model_fields_set:
+ _dict["maxDistance"] = None
+
+ # set to None if source_lat (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_lat is None and "source_lat" in self.model_fields_set:
+ _dict["sourceLat"] = None
+
+ # set to None if source_lon (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_lon is None and "source_lon" in self.model_fields_set:
+ _dict["sourceLon"] = None
+
+ # set to None if source_max_distance (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.source_max_distance is None
+ and "source_max_distance" in self.model_fields_set
+ ):
+ _dict["sourceMaxDistance"] = None
+
+ # set to None if min_word_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.min_word_count is None and "min_word_count" in self.model_fields_set:
+ _dict["minWordCount"] = None
+
+ # set to None if max_word_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.max_word_count is None and "max_word_count" in self.model_fields_set:
+ _dict["maxWordCount"] = None
+
+ # set to None if event_type (nullable) is None
+ # and model_fields_set contains the field
+ if self.event_type is None and "event_type" in self.model_fields_set:
+ _dict["eventType"] = None
+
+ # set to None if extractor_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.extractor_name is None and "extractor_name" in self.model_fields_set:
+ _dict["extractorName"] = None
+
+ # set to None if exclude_extractor_name (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_extractor_name is None
+ and "exclude_extractor_name" in self.model_fields_set
+ ):
+ _dict["excludeExtractorName"] = None
+
+ # set to None if exclude_source_country (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_source_country is None
+ and "exclude_source_country" in self.model_fields_set
+ ):
+ _dict["excludeSourceCountry"] = None
+
+ # set to None if exclude_source_county (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_source_county is None
+ and "exclude_source_county" in self.model_fields_set
+ ):
+ _dict["excludeSourceCounty"] = None
+
+ # set to None if author (nullable) is None
+ # and model_fields_set contains the field
+ if self.author is None and "author" in self.model_fields_set:
+ _dict["author"] = None
+
+ # set to None if exclude_author (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_author is None and "exclude_author" in self.model_fields_set:
+ _dict["excludeAuthor"] = None
+
+ # set to None if topic (nullable) is None
+ # and model_fields_set contains the field
+ if self.topic is None and "topic" in self.model_fields_set:
+ _dict["topic"] = None
+
+ # set to None if exclude_topic (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_topic is None and "exclude_topic" in self.model_fields_set:
+ _dict["excludeTopic"] = None
+
+ # set to None if category (nullable) is None
+ # and model_fields_set contains the field
+ if self.category is None and "category" in self.model_fields_set:
+ _dict["category"] = None
+
+ # set to None if taxonomy (nullable) is None
+ # and model_fields_set contains the field
+ if self.taxonomy is None and "taxonomy" in self.model_fields_set:
+ _dict["taxonomy"] = None
+
+ # set to None if prefix_taxonomy (nullable) is None
+ # and model_fields_set contains the field
+ if self.prefix_taxonomy is None and "prefix_taxonomy" in self.model_fields_set:
+ _dict["prefixTaxonomy"] = None
+
+ # set to None if company_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.company_id is None and "company_id" in self.model_fields_set:
+ _dict["companyId"] = None
+
+ # set to None if company_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.company_name is None and "company_name" in self.model_fields_set:
+ _dict["companyName"] = None
+
+ # set to None if company_domain (nullable) is None
+ # and model_fields_set contains the field
+ if self.company_domain is None and "company_domain" in self.model_fields_set:
+ _dict["companyDomain"] = None
+
+ # set to None if company_symbol (nullable) is None
+ # and model_fields_set contains the field
+ if self.company_symbol is None and "company_symbol" in self.model_fields_set:
+ _dict["companySymbol"] = None
+
+ # set to None if exclude_company_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_company_id is None
+ and "exclude_company_id" in self.model_fields_set
+ ):
+ _dict["excludeCompanyId"] = None
+
+ # set to None if exclude_company_domain (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_company_domain is None
+ and "exclude_company_domain" in self.model_fields_set
+ ):
+ _dict["excludeCompanyDomain"] = None
+
+ # set to None if exclude_company_symbol (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_company_symbol is None
+ and "exclude_company_symbol" in self.model_fields_set
+ ):
+ _dict["excludeCompanySymbol"] = None
+
+ # set to None if person_wikidata_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.person_wikidata_id is None
+ and "person_wikidata_id" in self.model_fields_set
+ ):
+ _dict["personWikidataId"] = None
+
+ # set to None if person_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.person_name is None and "person_name" in self.model_fields_set:
+ _dict["personName"] = None
+
+ # set to None if exclude_person_wikidata_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_person_wikidata_id is None
+ and "exclude_person_wikidata_id" in self.model_fields_set
+ ):
+ _dict["excludePersonWikidataId"] = None
+
+ # set to None if exclude_person_name (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_person_name is None
+ and "exclude_person_name" in self.model_fields_set
+ ):
+ _dict["excludePersonName"] = None
+
+ # set to None if link_to (nullable) is None
+ # and model_fields_set contains the field
+ if self.link_to is None and "link_to" in self.model_fields_set:
+ _dict["linkTo"] = None
+
+ # set to None if locations_country (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.locations_country is None
+ and "locations_country" in self.model_fields_set
+ ):
+ _dict["locationsCountry"] = None
+
+ # set to None if state (nullable) is None
+ # and model_fields_set contains the field
+ if self.state is None and "state" in self.model_fields_set:
+ _dict["state"] = None
+
+ # set to None if city (nullable) is None
+ # and model_fields_set contains the field
+ if self.city is None and "city" in self.model_fields_set:
+ _dict["city"] = None
+
+ # set to None if area (nullable) is None
+ # and model_fields_set contains the field
+ if self.area is None and "area" in self.model_fields_set:
+ _dict["area"] = None
+
+ # set to None if county (nullable) is None
+ # and model_fields_set contains the field
+ if self.county is None and "county" in self.model_fields_set:
+ _dict["county"] = None
+
+ # set to None if source_country (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_country is None and "source_country" in self.model_fields_set:
+ _dict["sourceCountry"] = None
+
+ # set to None if source_state (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_state is None and "source_state" in self.model_fields_set:
+ _dict["sourceState"] = None
+
+ # set to None if source_county (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_county is None and "source_county" in self.model_fields_set:
+ _dict["sourceCounty"] = None
+
+ # set to None if source_city (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_city is None and "source_city" in self.model_fields_set:
+ _dict["sourceCity"] = None
+
+ # set to None if exclude_locations_country (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_locations_country is None
+ and "exclude_locations_country" in self.model_fields_set
+ ):
+ _dict["excludeLocationsCountry"] = None
+
+ # set to None if exclude_state (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_state is None and "exclude_state" in self.model_fields_set:
+ _dict["excludeState"] = None
+
+ # set to None if exclude_county (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_county is None and "exclude_county" in self.model_fields_set:
+ _dict["excludeCounty"] = None
+
+ # set to None if exclude_city (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_city is None and "exclude_city" in self.model_fields_set:
+ _dict["excludeCity"] = None
+
+ # set to None if exclude_area (nullable) is None
+ # and model_fields_set contains the field
+ if self.exclude_area is None and "exclude_area" in self.model_fields_set:
+ _dict["excludeArea"] = None
+
+ # set to None if location (nullable) is None
+ # and model_fields_set contains the field
+ if self.location is None and "location" in self.model_fields_set:
+ _dict["location"] = None
+
+ # set to None if source_location (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_location is None and "source_location" in self.model_fields_set:
+ _dict["sourceLocation"] = None
+
+ # set to None if exclude_category (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_category is None
+ and "exclude_category" in self.model_fields_set
+ ):
+ _dict["excludeCategory"] = None
+
+ # set to None if exclude_taxonomy (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.exclude_taxonomy is None
+ and "exclude_taxonomy" in self.model_fields_set
+ ):
+ _dict["excludeTaxonomy"] = None
+
+ # set to None if positive_sentiment_from (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.positive_sentiment_from is None
+ and "positive_sentiment_from" in self.model_fields_set
+ ):
+ _dict["positiveSentimentFrom"] = None
+
+ # set to None if positive_sentiment_to (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.positive_sentiment_to is None
+ and "positive_sentiment_to" in self.model_fields_set
+ ):
+ _dict["positiveSentimentTo"] = None
+
+ # set to None if negative_sentiment_from (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.negative_sentiment_from is None
+ and "negative_sentiment_from" in self.model_fields_set
+ ):
+ _dict["negativeSentimentFrom"] = None
+
+ # set to None if negative_sentiment_to (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.negative_sentiment_to is None
+ and "negative_sentiment_to" in self.model_fields_set
+ ):
+ _dict["negativeSentimentTo"] = None
+
+ # set to None if neutral_sentiment_from (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.neutral_sentiment_from is None
+ and "neutral_sentiment_from" in self.model_fields_set
+ ):
+ _dict["neutralSentimentFrom"] = None
+
+ # set to None if neutral_sentiment_to (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.neutral_sentiment_to is None
+ and "neutral_sentiment_to" in self.model_fields_set
+ ):
+ _dict["neutralSentimentTo"] = None
+
+ # set to None if search_translation (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.search_translation is None
+ and "search_translation" in self.model_fields_set
+ ):
+ _dict["searchTranslation"] = None
+
+ # set to None if paywall (nullable) is None
+ # and model_fields_set contains the field
+ if self.paywall is None and "paywall" in self.model_fields_set:
+ _dict["paywall"] = None
+
+ # set to None if type (nullable) is None
+ # and model_fields_set contains the field
+ if self.type is None and "type" in self.model_fields_set:
+ _dict["type"] = None
+
+ # set to None if qfield (nullable) is None
+ # and model_fields_set contains the field
+ if self.qfield is None and "qfield" in self.model_fields_set:
+ _dict["qfield"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ComplexAllEndpointQuery from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "q": obj.get("q"),
+ "url": obj.get("url"),
+ "imageUrl": obj.get("imageUrl"),
+ "title": obj.get("title"),
+ "content": obj.get("content"),
+ "desc": obj.get("desc"),
+ "articleId": obj.get("articleId"),
+ "clusterId": obj.get("clusterId"),
+ "reprintGroupId": obj.get("reprintGroupId"),
+ "journalistId": obj.get("journalistId"),
+ "medium": obj.get("medium"),
+ "sourceGroup": obj.get("sourceGroup"),
+ "excludeSourceGroup": obj.get("excludeSourceGroup"),
+ "source": obj.get("source"),
+ "excludeSource": obj.get("excludeSource"),
+ "country": obj.get("country"),
+ "excludeCountry": obj.get("excludeCountry"),
+ "language": obj.get("language"),
+ "excludeLanguage": obj.get("excludeLanguage"),
+ "label": obj.get("label"),
+ "excludeLabel": obj.get("excludeLabel"),
+ "byline": obj.get("byline"),
+ "bylineQ": obj.get("bylineQ"),
+ "summary": obj.get("summary"),
+ "watchlist": obj.get("watchlist"),
+ "excludeWatchlist": obj.get("excludeWatchlist"),
+ "excludeJournalistId": obj.get("excludeJournalistId"),
+ "excludeSourceCity": obj.get("excludeSourceCity"),
+ "excludeSourceState": obj.get("excludeSourceState"),
+ "lat": obj.get("lat"),
+ "lon": obj.get("lon"),
+ "maxDistance": obj.get("maxDistance"),
+ "sourceLat": obj.get("sourceLat"),
+ "sourceLon": obj.get("sourceLon"),
+ "sourceMaxDistance": obj.get("sourceMaxDistance"),
+ "minWordCount": obj.get("minWordCount"),
+ "maxWordCount": obj.get("maxWordCount"),
+ "eventType": obj.get("eventType"),
+ "extractorName": obj.get("extractorName"),
+ "excludeExtractorName": obj.get("excludeExtractorName"),
+ "excludeSourceCountry": obj.get("excludeSourceCountry"),
+ "excludeSourceCounty": obj.get("excludeSourceCounty"),
+ "author": obj.get("author"),
+ "excludeAuthor": obj.get("excludeAuthor"),
+ "topic": obj.get("topic"),
+ "excludeTopic": obj.get("excludeTopic"),
+ "category": obj.get("category"),
+ "taxonomy": obj.get("taxonomy"),
+ "prefixTaxonomy": obj.get("prefixTaxonomy"),
+ "companyId": obj.get("companyId"),
+ "companyName": obj.get("companyName"),
+ "companyDomain": obj.get("companyDomain"),
+ "companySymbol": obj.get("companySymbol"),
+ "excludeCompanyId": obj.get("excludeCompanyId"),
+ "excludeCompanyDomain": obj.get("excludeCompanyDomain"),
+ "excludeCompanySymbol": obj.get("excludeCompanySymbol"),
+ "personWikidataId": obj.get("personWikidataId"),
+ "personName": obj.get("personName"),
+ "excludePersonWikidataId": obj.get("excludePersonWikidataId"),
+ "excludePersonName": obj.get("excludePersonName"),
+ "linkTo": obj.get("linkTo"),
+ "locationsCountry": obj.get("locationsCountry"),
+ "state": obj.get("state"),
+ "city": obj.get("city"),
+ "area": obj.get("area"),
+ "county": obj.get("county"),
+ "sourceCountry": obj.get("sourceCountry"),
+ "sourceState": obj.get("sourceState"),
+ "sourceCounty": obj.get("sourceCounty"),
+ "sourceCity": obj.get("sourceCity"),
+ "excludeLocationsCountry": obj.get("excludeLocationsCountry"),
+ "excludeState": obj.get("excludeState"),
+ "excludeCounty": obj.get("excludeCounty"),
+ "excludeCity": obj.get("excludeCity"),
+ "excludeArea": obj.get("excludeArea"),
+ "location": obj.get("location"),
+ "sourceLocation": obj.get("sourceLocation"),
+ "excludeCategory": obj.get("excludeCategory"),
+ "excludeTaxonomy": obj.get("excludeTaxonomy"),
+ "positiveSentimentFrom": obj.get("positiveSentimentFrom"),
+ "positiveSentimentTo": obj.get("positiveSentimentTo"),
+ "negativeSentimentFrom": obj.get("negativeSentimentFrom"),
+ "negativeSentimentTo": obj.get("negativeSentimentTo"),
+ "neutralSentimentFrom": obj.get("neutralSentimentFrom"),
+ "neutralSentimentTo": obj.get("neutralSentimentTo"),
+ "searchTranslation": obj.get("searchTranslation"),
+ "paywall": obj.get("paywall"),
+ "type": obj.get("type"),
+ "qfield": obj.get("qfield"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/contact_point_api_dto.py b/src/perigon/models/contact_point_api_dto.py
new file mode 100644
index 0000000..84ac470
--- /dev/null
+++ b/src/perigon/models/contact_point_api_dto.py
@@ -0,0 +1,238 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class ContactPointApiDto(BaseModel):
+ """
+ Contact point used to deliver monitor notifications.
+ """
+
+ uuid: Optional[UUID] = Field(
+ default=None, description="Unique identifier for the contact point."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the contact point was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the contact point was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ type: Optional[StrictStr] = Field(
+ default=None,
+ description="Delivery channel type. API-managed contact points use WEBHOOK.",
+ )
+ status: Optional[StrictStr] = Field(
+ default=None, description="Current lifecycle status of the contact point."
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Display name for the contact point."
+ )
+ email: Optional[StrictStr] = Field(
+ default=None,
+ description="Email address when the contact point type is email-based.",
+ )
+ webhook_url: Optional[StrictStr] = Field(
+ default=None,
+ description="HTTPS webhook URL that receives monitor notifications.",
+ alias="webhookUrl",
+ )
+ webhook_secret: Optional[StrictStr] = Field(
+ default=None,
+ description="Secret used to sign webhook deliveries.",
+ alias="webhookSecret",
+ )
+ verified_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the contact point was verified, in ISO 8601 format.",
+ alias="verifiedAt",
+ )
+ verified: Optional[StrictBool] = None
+ __properties: ClassVar[List[str]] = [
+ "uuid",
+ "createdAt",
+ "updatedAt",
+ "type",
+ "status",
+ "name",
+ "email",
+ "webhookUrl",
+ "webhookSecret",
+ "verifiedAt",
+ "verified",
+ ]
+
+ @field_validator("type")
+ def type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["EMAIL", "FASTN", "WEBHOOK"]):
+ raise ValueError("must be one of enum values ('EMAIL', 'FASTN', 'WEBHOOK')")
+ return value
+
+ @field_validator("status")
+ def status_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["ACTIVE", "STOPPED", "FAILING", "ARCHIVED"]):
+ raise ValueError(
+ "must be one of enum values ('ACTIVE', 'STOPPED', 'FAILING', 'ARCHIVED')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ContactPointApiDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if uuid (nullable) is None
+ # and model_fields_set contains the field
+ if self.uuid is None and "uuid" in self.model_fields_set:
+ _dict["uuid"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if type (nullable) is None
+ # and model_fields_set contains the field
+ if self.type is None and "type" in self.model_fields_set:
+ _dict["type"] = None
+
+ # set to None if status (nullable) is None
+ # and model_fields_set contains the field
+ if self.status is None and "status" in self.model_fields_set:
+ _dict["status"] = None
+
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if email (nullable) is None
+ # and model_fields_set contains the field
+ if self.email is None and "email" in self.model_fields_set:
+ _dict["email"] = None
+
+ # set to None if webhook_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.webhook_url is None and "webhook_url" in self.model_fields_set:
+ _dict["webhookUrl"] = None
+
+ # set to None if webhook_secret (nullable) is None
+ # and model_fields_set contains the field
+ if self.webhook_secret is None and "webhook_secret" in self.model_fields_set:
+ _dict["webhookSecret"] = None
+
+ # set to None if verified_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.verified_at is None and "verified_at" in self.model_fields_set:
+ _dict["verifiedAt"] = None
+
+ # set to None if verified (nullable) is None
+ # and model_fields_set contains the field
+ if self.verified is None and "verified" in self.model_fields_set:
+ _dict["verified"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ContactPointApiDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "uuid": obj.get("uuid"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "type": obj.get("type"),
+ "status": obj.get("status"),
+ "name": obj.get("name"),
+ "email": obj.get("email"),
+ "webhookUrl": obj.get("webhookUrl"),
+ "webhookSecret": obj.get("webhookSecret"),
+ "verifiedAt": obj.get("verifiedAt"),
+ "verified": obj.get("verified"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/contact_point_list_result.py b/src/perigon/models/contact_point_list_result.py
new file mode 100644
index 0000000..e390779
--- /dev/null
+++ b/src/perigon/models/contact_point_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.contact_point_api_dto import ContactPointApiDto
+
+
+class ContactPointListResult(BaseModel):
+ """
+ Paginated list of contact points.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching contact points across all pages."
+ )
+ data: List[ContactPointApiDto] = Field(
+ description="Contact points returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ContactPointListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ContactPointListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [ContactPointApiDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/contact_point_request_body.py b/src/perigon/models/contact_point_request_body.py
new file mode 100644
index 0000000..546e1e2
--- /dev/null
+++ b/src/perigon/models/contact_point_request_body.py
@@ -0,0 +1,111 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class ContactPointRequestBody(BaseModel):
+ """
+ ContactPointRequestBody
+ """
+
+ type: StrictStr
+ name: StrictStr
+ email: Optional[StrictStr] = None
+ webhook: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = ["type", "name", "email", "webhook"]
+
+ @field_validator("type")
+ def type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["EMAIL", "FASTN", "WEBHOOK"]):
+ raise ValueError("must be one of enum values ('EMAIL', 'FASTN', 'WEBHOOK')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ContactPointRequestBody from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if email (nullable) is None
+ # and model_fields_set contains the field
+ if self.email is None and "email" in self.model_fields_set:
+ _dict["email"] = None
+
+ # set to None if webhook (nullable) is None
+ # and model_fields_set contains the field
+ if self.webhook is None and "webhook" in self.model_fields_set:
+ _dict["webhook"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ContactPointRequestBody from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "type": obj.get("type"),
+ "name": obj.get("name"),
+ "email": obj.get("email"),
+ "webhook": obj.get("webhook"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/contact_point_result.py b/src/perigon/models/contact_point_result.py
new file mode 100644
index 0000000..ab31d15
--- /dev/null
+++ b/src/perigon/models/contact_point_result.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.contact_point_api_dto import ContactPointApiDto
+
+
+class ContactPointResult(BaseModel):
+ """
+ Single contact point response.
+ """
+
+ data: ContactPointApiDto
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ContactPointResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data
+ if self.data:
+ _dict["data"] = self.data.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ContactPointResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": ContactPointApiDto.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/contact_point_sort_by.py b/src/perigon/models/contact_point_sort_by.py
new file mode 100644
index 0000000..dc0cb42
--- /dev/null
+++ b/src/perigon/models/contact_point_sort_by.py
@@ -0,0 +1,37 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+from enum import Enum
+
+from typing_extensions import Self
+
+
+class ContactPointSortBy(str, Enum):
+ """
+ ContactPointSortBy
+ """
+
+ """
+ allowed enum values
+ """
+ CREATEDAT = "createdAt"
+ UPDATEDAT = "updatedAt"
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of ContactPointSortBy from a JSON string"""
+ return cls(json.loads(json_str))
diff --git a/src/perigon/models/contact_point_update_body.py b/src/perigon/models/contact_point_update_body.py
new file mode 100644
index 0000000..833088e
--- /dev/null
+++ b/src/perigon/models/contact_point_update_body.py
@@ -0,0 +1,119 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class ContactPointUpdateBody(BaseModel):
+ """
+ ContactPointUpdateBody
+ """
+
+ name: Optional[StrictStr] = None
+ status: Optional[StrictStr] = None
+ webhook: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = ["name", "status", "webhook"]
+
+ @field_validator("status")
+ def status_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["ACTIVE", "STOPPED", "FAILING", "ARCHIVED"]):
+ raise ValueError(
+ "must be one of enum values ('ACTIVE', 'STOPPED', 'FAILING', 'ARCHIVED')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ContactPointUpdateBody from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if status (nullable) is None
+ # and model_fields_set contains the field
+ if self.status is None and "status" in self.model_fields_set:
+ _dict["status"] = None
+
+ # set to None if webhook (nullable) is None
+ # and model_fields_set contains the field
+ if self.webhook is None and "webhook" in self.model_fields_set:
+ _dict["webhook"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ContactPointUpdateBody from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "status": obj.get("status"),
+ "webhook": obj.get("webhook"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/coordinate.py b/src/perigon/models/coordinate.py
similarity index 83%
rename from perigon/models/coordinate.py
rename to src/perigon/models/coordinate.py
index b836b89..800a923 100644
--- a/perigon/models/coordinate.py
+++ b/src/perigon/models/coordinate.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class Coordinate(BaseModel):
"""
- Coordinate
- """ # noqa: E501
+ Geographic coordinates of the place.
+ """
- lat: Optional[Union[StrictFloat, StrictInt]] = None
- lon: Optional[Union[StrictFloat, StrictInt]] = None
+ lat: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Latitude in decimal degrees."
+ )
+ lon: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Longitude in decimal degrees."
+ )
__properties: ClassVar[List[str]] = ["lat", "lon"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/coordinate_filter.py b/src/perigon/models/coordinate_filter.py
similarity index 83%
rename from perigon/models/coordinate_filter.py
rename to src/perigon/models/coordinate_filter.py
index b76d189..bcfff9b 100644
--- a/perigon/models/coordinate_filter.py
+++ b/src/perigon/models/coordinate_filter.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,22 +19,30 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class CoordinateFilter(BaseModel):
"""
Filter articles from publishers based at specific geographic locations. Uses latitude, longitude, and radius parameters to define a circular search area for publisher locations.
- """ # noqa: E501
+ """
- lat: Optional[Union[StrictFloat, StrictInt]] = None
- lon: Optional[Union[StrictFloat, StrictInt]] = None
- radius: Optional[Union[StrictFloat, StrictInt]] = None
+ lat: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, json_schema_extra={"examples": [0]}
+ )
+ lon: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, json_schema_extra={"examples": [0]}
+ )
+ radius: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, json_schema_extra={"examples": [0]}
+ )
__properties: ClassVar[List[str]] = ["lat", "lon", "radius"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/create_source_group_params.py b/src/perigon/models/create_source_group_params.py
similarity index 94%
rename from perigon/models/create_source_group_params.py
rename to src/perigon/models/create_source_group_params.py
index 670fe37..924a112 100644
--- a/perigon/models/create_source_group_params.py
+++ b/src/perigon/models/create_source_group_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
class CreateSourceGroupParams(BaseModel):
"""
CreateSourceGroupParams
- """ # noqa: E501
+ """
name: StrictStr
display_name: Optional[StrictStr] = Field(default=None, alias="displayName")
@@ -41,7 +41,8 @@ class CreateSourceGroupParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -52,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/create_watchlist_params.py b/src/perigon/models/create_watchlist_params.py
similarity index 83%
rename from perigon/models/create_watchlist_params.py
rename to src/perigon/models/create_watchlist_params.py
index a445780..fd1c935 100644
--- a/perigon/models/create_watchlist_params.py
+++ b/src/perigon/models/create_watchlist_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
from perigon.models.watchlist_company import WatchlistCompany
@@ -30,7 +30,7 @@
class CreateWatchlistParams(BaseModel):
"""
CreateWatchlistParams
- """ # noqa: E501
+ """
name: StrictStr = Field(
description="Unique name for the watchlist within the organization"
@@ -60,7 +60,8 @@ class CreateWatchlistParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -71,8 +72,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -100,15 +100,17 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.people:
for _item_people in self.people:
- if _item_people:
- _items.append(_item_people.to_dict())
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
_dict["people"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in companies (list)
_items = []
if self.companies:
for _item_companies in self.companies:
- if _item_companies:
- _items.append(_item_companies.to_dict())
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
_dict["companies"] = _items
# set to None if display_name (nullable) is None
# and model_fields_set contains the field
@@ -141,19 +143,15 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"name": obj.get("name"),
"displayName": obj.get("displayName"),
"description": obj.get("description"),
- "people": (
- [WatchlistPerson.from_dict(_item) for _item in obj["people"]]
- if obj.get("people") is not None
- else None
- ),
- "companies": (
- [WatchlistCompany.from_dict(_item) for _item in obj["companies"]]
- if obj.get("companies") is not None
- else None
- ),
- "visible": (
- obj.get("visible") if obj.get("visible") is not None else True
- ),
+ "people": [WatchlistPerson.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "companies": [
+ WatchlistCompany.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "visible": obj.get("visible") if "visible" in obj else True,
}
)
return _obj
diff --git a/src/perigon/models/entity_group.py b/src/perigon/models/entity_group.py
new file mode 100644
index 0000000..a7cd3d9
--- /dev/null
+++ b/src/perigon/models/entity_group.py
@@ -0,0 +1,101 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class EntityGroup(BaseModel):
+ """
+ Entity groups the monitor identifies in matching content.
+ """
+
+ group_reference: StrictStr = Field(
+ description="Reference name used to identify the entity group in monitor output.",
+ alias="groupReference",
+ )
+ description: Optional[StrictStr] = Field(
+ default=None,
+ description="Optional plain-language description of the entities represented by the group.",
+ )
+ __properties: ClassVar[List[str]] = ["groupReference", "description"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of EntityGroup from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of EntityGroup from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "groupReference": obj.get("groupReference"),
+ "description": obj.get("description"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/entity_holder.py b/src/perigon/models/entity_holder.py
similarity index 81%
rename from perigon/models/entity_holder.py
rename to src/perigon/models/entity_holder.py
index f1e27f0..08c20b2 100644
--- a/perigon/models/entity_holder.py
+++ b/src/perigon/models/entity_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,22 +19,31 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class EntityHolder(BaseModel):
"""
- EntityHolder
- """ # noqa: E501
+ Named entities extracted from the article.
+ """
- data: Optional[StrictStr] = None
- type: Optional[StrictStr] = None
- mentions: Optional[StrictInt] = None
+ data: Optional[StrictStr] = Field(
+ default=None, description="Text value of the extracted entity."
+ )
+ type: Optional[StrictStr] = Field(
+ default=None,
+ description="Entity type, such as PERSON, ORGANIZATION, or LOCATION.",
+ )
+ mentions: Optional[StrictInt] = Field(
+ default=None, description="Number of times the entity appears in the article."
+ )
__properties: ClassVar[List[str]] = ["data", "type", "mentions"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +54,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/error_response.py b/src/perigon/models/error_response.py
similarity index 94%
rename from perigon/models/error_response.py
rename to src/perigon/models/error_response.py
index 32ec17b..4aac10c 100644
--- a/perigon/models/error_response.py
+++ b/src/perigon/models/error_response.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class ErrorResponse(BaseModel):
"""
ErrorResponse
- """ # noqa: E501
+ """
status: Optional[StrictInt] = None
message: Optional[StrictStr] = None
@@ -35,7 +35,8 @@ class ErrorResponse(BaseModel):
__properties: ClassVar[List[str]] = ["status", "message", "timestamp"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +47,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/event_type_holder.py b/src/perigon/models/event_type_holder.py
similarity index 85%
rename from perigon/models/event_type_holder.py
rename to src/perigon/models/event_type_holder.py
index 62f811a..d241463 100644
--- a/perigon/models/event_type_holder.py
+++ b/src/perigon/models/event_type_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,25 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class EventTypeHolder(BaseModel):
"""
- EventTypeHolder
- """ # noqa: E501
+ Event types detected in the article.
+ """
- name: Optional[StrictStr] = None
- type: Optional[StrictStr] = None
+ name: Optional[StrictStr] = Field(default=None, description="Detected event name.")
+ type: Optional[StrictStr] = Field(
+ default=None, description="Detected event classification."
+ )
__properties: ClassVar[List[str]] = ["name", "type"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +48,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/auth_exception_cause.py b/src/perigon/models/fillbot_metadata.py
similarity index 58%
rename from perigon/models/auth_exception_cause.py
rename to src/perigon/models/fillbot_metadata.py
index 11a7d2f..aaa36d0 100644
--- a/perigon/models/auth_exception_cause.py
+++ b/src/perigon/models/fillbot_metadata.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,30 +19,28 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic import BaseModel, ConfigDict, Field
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
+from perigon.models.filled_field import FilledField
-class AuthExceptionCause(BaseModel):
+class FillbotMetadata(BaseModel):
+ """
+ Provenance for event fields populated by the enrichment process.
"""
- AuthExceptionCause
- """ # noqa: E501
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
+ filled_fields: Optional[List[FilledField]] = Field(
+ default=None,
+ description="Event fields populated by enrichment and the sources used for each field.",
+ alias="filledFields",
)
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
+ __properties: ClassVar[List[str]] = ["filledFields"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,12 +51,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of AuthExceptionCause from a JSON string"""
+ """Create an instance of FillbotMetadata from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -79,18 +75,26 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
+ # override the default output from pydantic by calling `to_dict()` of each item in filled_fields (list)
_items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
+ if self.filled_fields:
+ for _item_filled_fields in self.filled_fields:
+ _items.append(
+ _item_filled_fields.to_dict()
+ if _item_filled_fields is not None
+ else None
+ )
+ _dict["filledFields"] = _items
+ # set to None if filled_fields (nullable) is None
+ # and model_fields_set contains the field
+ if self.filled_fields is None and "filled_fields" in self.model_fields_set:
+ _dict["filledFields"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of AuthExceptionCause from a dict"""
+ """Create an instance of FillbotMetadata from a dict"""
if obj is None:
return None
@@ -99,16 +103,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
+ "filledFields": [
+ FilledField.from_dict(_item) for _item in obj["filledFields"]
+ ]
+ if obj.get("filledFields") is not None
+ else None
}
)
return _obj
diff --git a/src/perigon/models/filled_field.py b/src/perigon/models/filled_field.py
new file mode 100644
index 0000000..5b96009
--- /dev/null
+++ b/src/perigon/models/filled_field.py
@@ -0,0 +1,140 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.filled_field_source import FilledFieldSource
+
+
+class FilledField(BaseModel):
+ """
+ Event fields populated by enrichment and the sources used for each field.
+ """
+
+ var_field: StrictStr = Field(
+ description="Name of the event data field populated by enrichment.",
+ alias="field",
+ )
+ method: StrictStr = Field(
+ description="Search method used to populate the field. Accepts WEB_SEARCH or ARTICLE_SEARCH."
+ )
+ source: StrictStr = Field(
+ description="How enrichment was initiated. Accepts MANUAL or BACKGROUND."
+ )
+ sources: Optional[List[FilledFieldSource]] = Field(
+ default=None, description="Sources used to populate the field."
+ )
+ __properties: ClassVar[List[str]] = ["field", "method", "source", "sources"]
+
+ @field_validator("method")
+ def method_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(
+ ["WEB_SEARCH", "ARTICLE_SEARCH", "LINKEDIN_POST", "REDDIT_POST", "X_POST"]
+ ):
+ raise ValueError(
+ "must be one of enum values ('WEB_SEARCH', 'ARTICLE_SEARCH', 'LINKEDIN_POST', 'REDDIT_POST', 'X_POST')"
+ )
+ return value
+
+ @field_validator("source")
+ def source_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["MANUAL", "BACKGROUND"]):
+ raise ValueError("must be one of enum values ('MANUAL', 'BACKGROUND')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of FilledField from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in sources (list)
+ _items = []
+ if self.sources:
+ for _item_sources in self.sources:
+ _items.append(
+ _item_sources.to_dict() if _item_sources is not None else None
+ )
+ _dict["sources"] = _items
+ # set to None if sources (nullable) is None
+ # and model_fields_set contains the field
+ if self.sources is None and "sources" in self.model_fields_set:
+ _dict["sources"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of FilledField from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "field": obj.get("field"),
+ "method": obj.get("method"),
+ "source": obj.get("source"),
+ "sources": [
+ FilledFieldSource.from_dict(_item) for _item in obj["sources"]
+ ]
+ if obj.get("sources") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/filled_field_source.py b/src/perigon/models/filled_field_source.py
new file mode 100644
index 0000000..61728aa
--- /dev/null
+++ b/src/perigon/models/filled_field_source.py
@@ -0,0 +1,122 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class FilledFieldSource(BaseModel):
+ """
+ Sources used to populate the field.
+ """
+
+ article_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Perigon article ID of the supporting source, when available.",
+ alias="articleId",
+ )
+ url: Optional[StrictStr] = Field(
+ default=None, description="URL of the supporting source, when available."
+ )
+ title: Optional[StrictStr] = Field(
+ default=None, description="Title of the supporting source, when available."
+ )
+ x_post_id: Optional[StrictStr] = Field(default=None, alias="xPostId")
+ __properties: ClassVar[List[str]] = ["articleId", "url", "title", "xPostId"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of FilledFieldSource from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if article_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.article_id is None and "article_id" in self.model_fields_set:
+ _dict["articleId"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if x_post_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_post_id is None and "x_post_id" in self.model_fields_set:
+ _dict["xPostId"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of FilledFieldSource from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "articleId": obj.get("articleId"),
+ "url": obj.get("url"),
+ "title": obj.get("title"),
+ "xPostId": obj.get("xPostId"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/id_name_holder.py b/src/perigon/models/id_name_holder.py
similarity index 83%
rename from perigon/models/id_name_holder.py
rename to src/perigon/models/id_name_holder.py
index d75f59b..99188b2 100644
--- a/perigon/models/id_name_holder.py
+++ b/src/perigon/models/id_name_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class IdNameHolder(BaseModel):
"""
- IdNameHolder
- """ # noqa: E501
+ Known journalist records matched to the article byline.
+ """
- id: Optional[StrictStr] = None
- name: Optional[StrictStr] = None
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique identifier for the matched record."
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Display name of the matched record."
+ )
__properties: ClassVar[List[str]] = ["id", "name"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/image_holder.py b/src/perigon/models/image_holder.py
similarity index 92%
rename from perigon/models/image_holder.py
rename to src/perigon/models/image_holder.py
index 053f1a7..83b721c 100644
--- a/perigon/models/image_holder.py
+++ b/src/perigon/models/image_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,19 +20,21 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class ImageHolder(BaseModel):
"""
ImageHolder
- """ # noqa: E501
+ """
url: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = ["url"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -44,8 +45,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/journalist.py b/src/perigon/models/journalist.py
similarity index 62%
rename from perigon/models/journalist.py
rename to src/perigon/models/journalist.py
index 2979efe..340931f 100644
--- a/perigon/models/journalist.py
+++ b/src/perigon/models/journalist.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.location_holder import LocationHolder
@@ -29,50 +29,137 @@
class Journalist(BaseModel):
"""
- Journalist
- """ # noqa: E501
-
- id: Optional[StrictStr] = None
- name: Optional[StrictStr] = None
- full_name: Optional[StrictStr] = Field(default=None, alias="fullName")
- headline: Optional[StrictStr] = None
- description: Optional[StrictStr] = None
- title: Optional[StrictStr] = None
- locations: Optional[List[LocationHolder]] = None
- updated_at: Optional[StrictStr] = Field(default=None, alias="updatedAt")
- top_topics: Optional[List[NameCount]] = Field(default=None, alias="topTopics")
- top_sources: Optional[List[NameCount]] = Field(default=None, alias="topSources")
+ Expanded journalist details, when requested and available.
+ """
+
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique Perigon identifier for the journalist."
+ )
+ primary_record_id: Optional[StrictStr] = Field(
+ default=None, alias="primaryRecordId"
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Common display name of the journalist."
+ )
+ full_name: Optional[StrictStr] = Field(
+ default=None, description="Full name of the journalist.", alias="fullName"
+ )
+ headline: Optional[StrictStr] = Field(
+ default=None,
+ description="Professional headline associated with the journalist.",
+ )
+ description: Optional[StrictStr] = Field(
+ default=None, description="Biographical description of the journalist."
+ )
+ title: Optional[StrictStr] = Field(
+ default=None, description="Professional title of the journalist."
+ )
+ email: Optional[StrictStr] = Field(default=None, description="Journalist's email.")
+ locations: Optional[List[LocationHolder]] = Field(
+ default=None, description="Locations associated with the journalist."
+ )
+ updated_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the journalist record was last refreshed, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ top_topics: Optional[List[NameCount]] = Field(
+ default=None,
+ description="Topics most frequently covered by the journalist.",
+ alias="topTopics",
+ )
+ top_sources: Optional[List[NameCount]] = Field(
+ default=None,
+ description="Publishers most frequently associated with the journalist.",
+ alias="topSources",
+ )
top_categories: Optional[List[NameCount]] = Field(
- default=None, alias="topCategories"
+ default=None,
+ description="Categories most frequently covered by the journalist.",
+ alias="topCategories",
+ )
+ top_labels: Optional[List[NameCount]] = Field(
+ default=None,
+ description="Editorial labels most frequently associated with the journalist's articles.",
+ alias="topLabels",
+ )
+ top_countries: Optional[List[NameCount]] = Field(
+ default=None,
+ description="Countries most frequently associated with the journalist's articles.",
+ alias="topCountries",
)
- top_labels: Optional[List[NameCount]] = Field(default=None, alias="topLabels")
- top_countries: Optional[List[NameCount]] = Field(default=None, alias="topCountries")
avg_monthly_posts: Optional[StrictInt] = Field(
- default=None, alias="avgMonthlyPosts"
+ default=None,
+ description="Average number of articles published by the journalist per month.",
+ alias="avgMonthlyPosts",
+ )
+ twitter_handle: Optional[StrictStr] = Field(
+ default=None,
+ description="Journalist's X or Twitter handle.",
+ alias="twitterHandle",
+ )
+ twitter_bio: Optional[StrictStr] = Field(
+ default=None,
+ description="Biography from the journalist's X or Twitter profile.",
+ alias="twitterBio",
+ )
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Profile image URL for the journalist.",
+ alias="imageUrl",
+ )
+ linkedin_url: Optional[StrictStr] = Field(
+ default=None,
+ description="LinkedIn profile URL for the journalist.",
+ alias="linkedinUrl",
)
- twitter_handle: Optional[StrictStr] = Field(default=None, alias="twitterHandle")
- twitter_bio: Optional[StrictStr] = Field(default=None, alias="twitterBio")
- image_url: Optional[StrictStr] = Field(default=None, alias="imageUrl")
- linkedin_url: Optional[StrictStr] = Field(default=None, alias="linkedinUrl")
linkedin_connections: Optional[StrictInt] = Field(
- default=None, alias="linkedinConnections"
+ default=None,
+ description="LinkedIn connection count for the journalist, when available.",
+ alias="linkedinConnections",
)
linkedin_followers: Optional[StrictInt] = Field(
- default=None, alias="linkedinFollowers"
+ default=None,
+ description="LinkedIn follower count for the journalist, when available.",
+ alias="linkedinFollowers",
+ )
+ facebook_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Facebook profile URL for the journalist.",
+ alias="facebookUrl",
+ )
+ instagram_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Instagram profile URL for the journalist.",
+ alias="instagramUrl",
+ )
+ website_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Personal or professional website URL for the journalist.",
+ alias="websiteUrl",
+ )
+ blog_url: Optional[StrictStr] = Field(
+ default=None, description="Blog URL for the journalist.", alias="blogUrl"
+ )
+ tumblr_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Tumblr profile URL for the journalist.",
+ alias="tumblrUrl",
+ )
+ youtube_url: Optional[StrictStr] = Field(
+ default=None,
+ description="YouTube channel URL for the journalist.",
+ alias="youtubeUrl",
)
- facebook_url: Optional[StrictStr] = Field(default=None, alias="facebookUrl")
- instagram_url: Optional[StrictStr] = Field(default=None, alias="instagramUrl")
- website_url: Optional[StrictStr] = Field(default=None, alias="websiteUrl")
- blog_url: Optional[StrictStr] = Field(default=None, alias="blogUrl")
- tumblr_url: Optional[StrictStr] = Field(default=None, alias="tumblrUrl")
- youtube_url: Optional[StrictStr] = Field(default=None, alias="youtubeUrl")
__properties: ClassVar[List[str]] = [
"id",
+ "primaryRecordId",
"name",
"fullName",
"headline",
"description",
"title",
+ "email",
"locations",
"updatedAt",
"topTopics",
@@ -96,7 +183,8 @@ class Journalist(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -107,8 +195,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -136,49 +223,69 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.locations:
for _item_locations in self.locations:
- if _item_locations:
- _items.append(_item_locations.to_dict())
+ _items.append(
+ _item_locations.to_dict() if _item_locations is not None else None
+ )
_dict["locations"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_topics (list)
_items = []
if self.top_topics:
for _item_top_topics in self.top_topics:
- if _item_top_topics:
- _items.append(_item_top_topics.to_dict())
+ _items.append(
+ _item_top_topics.to_dict() if _item_top_topics is not None else None
+ )
_dict["topTopics"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_sources (list)
_items = []
if self.top_sources:
for _item_top_sources in self.top_sources:
- if _item_top_sources:
- _items.append(_item_top_sources.to_dict())
+ _items.append(
+ _item_top_sources.to_dict()
+ if _item_top_sources is not None
+ else None
+ )
_dict["topSources"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_categories (list)
_items = []
if self.top_categories:
for _item_top_categories in self.top_categories:
- if _item_top_categories:
- _items.append(_item_top_categories.to_dict())
+ _items.append(
+ _item_top_categories.to_dict()
+ if _item_top_categories is not None
+ else None
+ )
_dict["topCategories"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_labels (list)
_items = []
if self.top_labels:
for _item_top_labels in self.top_labels:
- if _item_top_labels:
- _items.append(_item_top_labels.to_dict())
+ _items.append(
+ _item_top_labels.to_dict() if _item_top_labels is not None else None
+ )
_dict["topLabels"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_countries (list)
_items = []
if self.top_countries:
for _item_top_countries in self.top_countries:
- if _item_top_countries:
- _items.append(_item_top_countries.to_dict())
+ _items.append(
+ _item_top_countries.to_dict()
+ if _item_top_countries is not None
+ else None
+ )
_dict["topCountries"] = _items
# set to None if id (nullable) is None
# and model_fields_set contains the field
if self.id is None and "id" in self.model_fields_set:
_dict["id"] = None
+ # set to None if primary_record_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.primary_record_id is None
+ and "primary_record_id" in self.model_fields_set
+ ):
+ _dict["primaryRecordId"] = None
+
# set to None if name (nullable) is None
# and model_fields_set contains the field
if self.name is None and "name" in self.model_fields_set:
@@ -204,6 +311,11 @@ def to_dict(self) -> Dict[str, Any]:
if self.title is None and "title" in self.model_fields_set:
_dict["title"] = None
+ # set to None if email (nullable) is None
+ # and model_fields_set contains the field
+ if self.email is None and "email" in self.model_fields_set:
+ _dict["email"] = None
+
# set to None if locations (nullable) is None
# and model_fields_set contains the field
if self.locations is None and "locations" in self.model_fields_set:
@@ -327,42 +439,40 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"id": obj.get("id"),
+ "primaryRecordId": obj.get("primaryRecordId"),
"name": obj.get("name"),
"fullName": obj.get("fullName"),
"headline": obj.get("headline"),
"description": obj.get("description"),
"title": obj.get("title"),
- "locations": (
- [LocationHolder.from_dict(_item) for _item in obj["locations"]]
- if obj.get("locations") is not None
- else None
- ),
+ "email": obj.get("email"),
+ "locations": [
+ LocationHolder.from_dict(_item) for _item in obj["locations"]
+ ]
+ if obj.get("locations") is not None
+ else None,
"updatedAt": obj.get("updatedAt"),
- "topTopics": (
- [NameCount.from_dict(_item) for _item in obj["topTopics"]]
- if obj.get("topTopics") is not None
- else None
- ),
- "topSources": (
- [NameCount.from_dict(_item) for _item in obj["topSources"]]
- if obj.get("topSources") is not None
- else None
- ),
- "topCategories": (
- [NameCount.from_dict(_item) for _item in obj["topCategories"]]
- if obj.get("topCategories") is not None
- else None
- ),
- "topLabels": (
- [NameCount.from_dict(_item) for _item in obj["topLabels"]]
- if obj.get("topLabels") is not None
- else None
- ),
- "topCountries": (
- [NameCount.from_dict(_item) for _item in obj["topCountries"]]
- if obj.get("topCountries") is not None
- else None
- ),
+ "topTopics": [NameCount.from_dict(_item) for _item in obj["topTopics"]]
+ if obj.get("topTopics") is not None
+ else None,
+ "topSources": [
+ NameCount.from_dict(_item) for _item in obj["topSources"]
+ ]
+ if obj.get("topSources") is not None
+ else None,
+ "topCategories": [
+ NameCount.from_dict(_item) for _item in obj["topCategories"]
+ ]
+ if obj.get("topCategories") is not None
+ else None,
+ "topLabels": [NameCount.from_dict(_item) for _item in obj["topLabels"]]
+ if obj.get("topLabels") is not None
+ else None,
+ "topCountries": [
+ NameCount.from_dict(_item) for _item in obj["topCountries"]
+ ]
+ if obj.get("topCountries") is not None
+ else None,
"avgMonthlyPosts": obj.get("avgMonthlyPosts"),
"twitterHandle": obj.get("twitterHandle"),
"twitterBio": obj.get("twitterBio"),
diff --git a/perigon/models/journalist_search_result.py b/src/perigon/models/journalist_search_result.py
similarity index 85%
rename from perigon/models/journalist_search_result.py
rename to src/perigon/models/journalist_search_result.py
index 977a3ee..144cb01 100644
--- a/perigon/models/journalist_search_result.py
+++ b/src/perigon/models/journalist_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.journalist import Journalist
@@ -29,7 +29,7 @@
class JournalistSearchResult(BaseModel):
"""
Journalist search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class JournalistSearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [Journalist.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [Journalist.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/src/perigon/models/journalists_sort_by.py b/src/perigon/models/journalists_sort_by.py
new file mode 100644
index 0000000..27c8f7d
--- /dev/null
+++ b/src/perigon/models/journalists_sort_by.py
@@ -0,0 +1,38 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+from enum import Enum
+
+from typing_extensions import Self
+
+
+class JournalistsSortBy(str, Enum):
+ """
+ JournalistsSortBy
+ """
+
+ """
+ allowed enum values
+ """
+ AVGMONTHLYPOSTS = "avgMonthlyPosts"
+ LINKEDINCONNECTIONS = "linkedinConnections"
+ LINKEDINFOLLOWERS = "linkedinFollowers"
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of JournalistsSortBy from a JSON string"""
+ return cls(json.loads(json_str))
diff --git a/perigon/models/key_point.py b/src/perigon/models/key_point.py
similarity index 83%
rename from perigon/models/key_point.py
rename to src/perigon/models/key_point.py
index 0405ae8..dc8ef10 100644
--- a/perigon/models/key_point.py
+++ b/src/perigon/models/key_point.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class KeyPoint(BaseModel):
"""
- KeyPoint
- """ # noqa: E501
+ Key facts and developments identified in the story.
+ """
- point: Optional[StrictStr] = None
- references: Optional[List[StrictStr]] = None
+ point: Optional[StrictStr] = Field(
+ default=None, description="A key fact or development identified in the content."
+ )
+ references: Optional[List[StrictStr]] = Field(
+ default=None, description="Article IDs supporting the key point."
+ )
__properties: ClassVar[List[str]] = ["point", "references"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/keyword_holder.py b/src/perigon/models/keyword_holder.py
similarity index 82%
rename from perigon/models/keyword_holder.py
rename to src/perigon/models/keyword_holder.py
index a1e49a8..89777b4 100644
--- a/perigon/models/keyword_holder.py
+++ b/src/perigon/models/keyword_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class KeywordHolder(BaseModel):
"""
- KeywordHolder
- """ # noqa: E501
+ Keywords extracted from the article with their relevance weights.
+ """
- name: Optional[StrictStr] = None
- weight: Optional[Union[StrictFloat, StrictInt]] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Keyword extracted from the article."
+ )
+ weight: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Relative relevance weight of the keyword."
+ )
__properties: ClassVar[List[str]] = ["name", "weight"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/label_holder.py b/src/perigon/models/label_holder.py
similarity index 85%
rename from perigon/models/label_holder.py
rename to src/perigon/models/label_holder.py
index 548b676..9ff87a1 100644
--- a/perigon/models/label_holder.py
+++ b/src/perigon/models/label_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,20 +19,24 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class LabelHolder(BaseModel):
"""
- LabelHolder
- """ # noqa: E501
+ Editorial labels assigned to the article.
+ """
- name: Optional[StrictStr] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Editorial label assigned to the article."
+ )
__properties: ClassVar[List[str]] = ["name"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -44,8 +47,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/limits_result.py b/src/perigon/models/limits_result.py
new file mode 100644
index 0000000..92f6d2f
--- /dev/null
+++ b/src/perigon/models/limits_result.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.api_limits_dto import ApiLimitsDto
+
+
+class LimitsResult(BaseModel):
+ """
+ API usage and limits response.
+ """
+
+ data: ApiLimitsDto
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of LimitsResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data
+ if self.data:
+ _dict["data"] = self.data.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of LimitsResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": ApiLimitsDto.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/perigon/models/location_count.py b/src/perigon/models/location_count.py
similarity index 78%
rename from perigon/models/location_count.py
rename to src/perigon/models/location_count.py
index 65f5a55..6632cf0 100644
--- a/perigon/models/location_count.py
+++ b/src/perigon/models/location_count.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,36 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class LocationCount(BaseModel):
"""
- LocationCount
- """ # noqa: E501
-
- country: Optional[StrictStr] = None
- state: Optional[StrictStr] = None
- county: Optional[StrictStr] = None
- city: Optional[StrictStr] = None
- area: Optional[StrictStr] = None
- count: Optional[StrictInt] = None
+ Locations associated with the story and their article counts.
+ """
+
+ country: Optional[StrictStr] = Field(
+ default=None, description="Country associated with the story."
+ )
+ state: Optional[StrictStr] = Field(
+ default=None, description="State or region associated with the story."
+ )
+ county: Optional[StrictStr] = Field(
+ default=None, description="County associated with the story."
+ )
+ city: Optional[StrictStr] = Field(
+ default=None, description="City associated with the story."
+ )
+ area: Optional[StrictStr] = Field(
+ default=None,
+ description="Neighborhood, borough, or district associated with the story.",
+ )
+ count: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of story articles associated with the location.",
+ )
__properties: ClassVar[List[str]] = [
"country",
"state",
@@ -45,7 +59,8 @@ class LocationCount(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -56,8 +71,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/location_holder.py b/src/perigon/models/location_holder.py
similarity index 79%
rename from perigon/models/location_holder.py
rename to src/perigon/models/location_holder.py
index bcdbdbb..24bcecd 100644
--- a/perigon/models/location_holder.py
+++ b/src/perigon/models/location_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,24 +19,37 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class LocationHolder(BaseModel):
"""
- LocationHolder
- """ # noqa: E501
-
- country: Optional[StrictStr] = None
- state: Optional[StrictStr] = None
- county: Optional[StrictStr] = None
- city: Optional[StrictStr] = None
- area: Optional[StrictStr] = None
+ Locations most strongly associated with the story.
+ """
+
+ country: Optional[StrictStr] = Field(
+ default=None, description="Country central to the article."
+ )
+ state: Optional[StrictStr] = Field(
+ default=None, description="State or region central to the article."
+ )
+ county: Optional[StrictStr] = Field(
+ default=None, description="County central to the article."
+ )
+ city: Optional[StrictStr] = Field(
+ default=None, description="City central to the article."
+ )
+ area: Optional[StrictStr] = Field(
+ default=None,
+ description="Neighborhood, borough, or district central to the article.",
+ )
__properties: ClassVar[List[str]] = ["country", "state", "county", "city", "area"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +60,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/matched_entity_data.py b/src/perigon/models/matched_entity_data.py
new file mode 100644
index 0000000..e9ccbf9
--- /dev/null
+++ b/src/perigon/models/matched_entity_data.py
@@ -0,0 +1,102 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class MatchedEntityData(BaseModel):
+ """
+ Entities matched in the content that produced the event.
+ """
+
+ name: StrictStr = Field(
+ description="Name of the entity matched in the source content."
+ )
+ type: StrictStr = Field(description="Entity type assigned by the monitor.")
+ domain: Optional[StrictStr] = Field(
+ default=None,
+ description="Domain associated with the matched entity, when available.",
+ )
+ __properties: ClassVar[List[str]] = ["name", "type", "domain"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MatchedEntityData from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if domain (nullable) is None
+ # and model_fields_set contains the field
+ if self.domain is None and "domain" in self.model_fields_set:
+ _dict["domain"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MatchedEntityData from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "type": obj.get("type"),
+ "domain": obj.get("domain"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/monitor_event_list_result.py b/src/perigon/models/monitor_event_list_result.py
new file mode 100644
index 0000000..8671143
--- /dev/null
+++ b/src/perigon/models/monitor_event_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_event_dto import SignalEventDto
+
+
+class MonitorEventListResult(BaseModel):
+ """
+ Paginated list of monitor events.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching events across all pages."
+ )
+ data: List[SignalEventDto] = Field(
+ description="Events returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MonitorEventListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MonitorEventListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [SignalEventDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/monitor_list_result.py b/src/perigon/models/monitor_list_result.py
new file mode 100644
index 0000000..49ad9c6
--- /dev/null
+++ b/src/perigon/models/monitor_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_api_dto import SignalApiDto
+
+
+class MonitorListResult(BaseModel):
+ """
+ Paginated list of monitors.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching monitors across all pages."
+ )
+ data: List[SignalApiDto] = Field(
+ description="Monitors returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MonitorListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MonitorListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [SignalApiDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/monitor_newsletter_list_result.py b/src/perigon/models/monitor_newsletter_list_result.py
new file mode 100644
index 0000000..8832f88
--- /dev/null
+++ b/src/perigon/models/monitor_newsletter_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_newsletter_dto import SignalNewsletterDto
+
+
+class MonitorNewsletterListResult(BaseModel):
+ """
+ Paginated list of monitor newsletters.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching newsletters across all pages."
+ )
+ data: List[SignalNewsletterDto] = Field(
+ description="Newsletters returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MonitorNewsletterListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MonitorNewsletterListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [SignalNewsletterDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/monitor_result.py b/src/perigon/models/monitor_result.py
new file mode 100644
index 0000000..4002102
--- /dev/null
+++ b/src/perigon/models/monitor_result.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_api_dto import SignalApiDto
+
+
+class MonitorResult(BaseModel):
+ """
+ Single monitor response.
+ """
+
+ data: SignalApiDto
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MonitorResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data
+ if self.data:
+ _dict["data"] = self.data.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MonitorResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": SignalApiDto.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/monitor_summary_list_result.py b/src/perigon/models/monitor_summary_list_result.py
new file mode 100644
index 0000000..4c52fc1
--- /dev/null
+++ b/src/perigon/models/monitor_summary_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_summary_dto import SignalSummaryDto
+
+
+class MonitorSummaryListResult(BaseModel):
+ """
+ Paginated list of monitor summaries.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching summaries across all pages."
+ )
+ data: List[SignalSummaryDto] = Field(
+ description="Summaries returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of MonitorSummaryListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of MonitorSummaryListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [SignalSummaryDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/perigon/models/name_count.py b/src/perigon/models/name_count.py
similarity index 83%
rename from perigon/models/name_count.py
rename to src/perigon/models/name_count.py
index a7851ff..7775870 100644
--- a/perigon/models/name_count.py
+++ b/src/perigon/models/name_count.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class NameCount(BaseModel):
"""
- NameCount
- """ # noqa: E501
+ Countries most frequently associated with the journalist's articles.
+ """
- name: Optional[StrictStr] = None
- count: Optional[StrictInt] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Name of the aggregated value."
+ )
+ count: Optional[StrictInt] = Field(
+ default=None, description="Number of matching records for the value."
+ )
__properties: ClassVar[List[str]] = ["name", "count"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/news_cluster.py b/src/perigon/models/news_cluster.py
new file mode 100644
index 0000000..4fb5aeb
--- /dev/null
+++ b/src/perigon/models/news_cluster.py
@@ -0,0 +1,722 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set, Union
+
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.category_holder import CategoryHolder
+from perigon.models.company_count import CompanyCount
+from perigon.models.company_holder import CompanyHolder
+from perigon.models.key_point import KeyPoint
+from perigon.models.location_count import LocationCount
+from perigon.models.location_holder import LocationHolder
+from perigon.models.news_cluster_article import NewsClusterArticle
+from perigon.models.person_count import PersonCount
+from perigon.models.person_holder import PersonHolder
+from perigon.models.question import Question
+from perigon.models.record_stat_holder import RecordStatHolder
+from perigon.models.sentiment_holder import SentimentHolder
+from perigon.models.source_holder import SourceHolder
+from perigon.models.topic_holder import TopicHolder
+
+
+class NewsCluster(BaseModel):
+ """
+ Expanded story cluster details, when requested and available.
+ """
+
+ created_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ initialized_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story became initialized and visible, in ISO 8601 format.",
+ alias="initializedAt",
+ )
+ id: Optional[StrictStr] = Field(
+ default=None, description="Unique identifier for the story cluster."
+ )
+ duplicate_of: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier of the original story when this story is a duplicate.",
+ alias="duplicateOf",
+ )
+ slug: Optional[StrictStr] = Field(
+ default=None, description="URL-safe identifier for the story."
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Generated story title."
+ )
+ summary: Optional[StrictStr] = Field(
+ default=None, description="Generated story summary."
+ )
+ short_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Short generated story summary, when available.",
+ alias="shortSummary",
+ )
+ summary_references: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Article IDs supporting the story summary.",
+ alias="summaryReferences",
+ )
+ image_source: Optional[SourceHolder] = Field(default=None, alias="imageSource")
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Primary image URL selected for the story.",
+ alias="imageUrl",
+ )
+ key_points: Optional[List[KeyPoint]] = Field(
+ default=None,
+ description="Key facts and developments identified in the story.",
+ alias="keyPoints",
+ )
+ questions: Optional[List[Question]] = Field(
+ default=None,
+ description="Questions and answers generated from the story content.",
+ )
+ unique_sources: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Distinct publisher domains represented in the story.",
+ alias="uniqueSources",
+ )
+ selected_articles: Optional[List[NewsClusterArticle]] = Field(
+ default=None,
+ description="Representative articles selected for the story.",
+ alias="selectedArticles",
+ )
+ sentiment: Optional[SentimentHolder] = None
+ unique_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of unique articles in the story.",
+ alias="uniqueCount",
+ )
+ reprint_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Number of reprinted articles in the story.",
+ alias="reprintCount",
+ )
+ total_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Total article count including reprints.",
+ alias="totalCount",
+ )
+ countries: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Countries associated with the story and their article counts.",
+ )
+ top_countries: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Countries most strongly associated with the story.",
+ alias="topCountries",
+ )
+ topics: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Topics associated with the story and their article counts.",
+ )
+ top_topics: Optional[List[TopicHolder]] = Field(
+ default=None,
+ description="Topics most strongly associated with the story.",
+ alias="topTopics",
+ )
+ categories: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Categories associated with the story and their article counts.",
+ )
+ top_categories: Optional[List[CategoryHolder]] = Field(
+ default=None,
+ description="Categories most strongly associated with the story.",
+ alias="topCategories",
+ )
+ taxonomies: Optional[List[RecordStatHolder]] = Field(
+ default=None,
+ description="Google Content Categories associated with the story and their article counts.",
+ )
+ top_taxonomies: Optional[List[CategoryHolder]] = Field(
+ default=None,
+ description="Google Content Categories most strongly associated with the story.",
+ alias="topTaxonomies",
+ )
+ people: Optional[List[PersonCount]] = Field(
+ default=None,
+ description="People associated with the story and their article counts.",
+ )
+ top_people: Optional[List[PersonHolder]] = Field(
+ default=None,
+ description="People most strongly associated with the story.",
+ alias="topPeople",
+ )
+ companies: Optional[List[CompanyCount]] = Field(
+ default=None,
+ description="Companies associated with the story and their article counts.",
+ )
+ top_companies: Optional[List[CompanyHolder]] = Field(
+ default=None,
+ description="Companies most strongly associated with the story.",
+ alias="topCompanies",
+ )
+ locations: Optional[List[LocationCount]] = Field(
+ default=None,
+ description="Locations associated with the story and their article counts.",
+ )
+ top_locations: Optional[List[LocationHolder]] = Field(
+ default=None,
+ description="Locations most strongly associated with the story.",
+ alias="topLocations",
+ )
+ highlights: Optional[Dict[str, List[StrictStr]]] = Field(
+ default=None, description="Matched text fragments grouped by story field."
+ )
+ source_diversity: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Ratio of unique publisher domains to total articles in the story.",
+ alias="sourceDiversity",
+ )
+ __properties: ClassVar[List[str]] = [
+ "createdAt",
+ "updatedAt",
+ "initializedAt",
+ "id",
+ "duplicateOf",
+ "slug",
+ "name",
+ "summary",
+ "shortSummary",
+ "summaryReferences",
+ "imageSource",
+ "imageUrl",
+ "keyPoints",
+ "questions",
+ "uniqueSources",
+ "selectedArticles",
+ "sentiment",
+ "uniqueCount",
+ "reprintCount",
+ "totalCount",
+ "countries",
+ "topCountries",
+ "topics",
+ "topTopics",
+ "categories",
+ "topCategories",
+ "taxonomies",
+ "topTaxonomies",
+ "people",
+ "topPeople",
+ "companies",
+ "topCompanies",
+ "locations",
+ "topLocations",
+ "highlights",
+ "sourceDiversity",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of NewsCluster from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of image_source
+ if self.image_source:
+ _dict["imageSource"] = self.image_source.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in key_points (list)
+ _items = []
+ if self.key_points:
+ for _item_key_points in self.key_points:
+ _items.append(
+ _item_key_points.to_dict() if _item_key_points is not None else None
+ )
+ _dict["keyPoints"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in questions (list)
+ _items = []
+ if self.questions:
+ for _item_questions in self.questions:
+ _items.append(
+ _item_questions.to_dict() if _item_questions is not None else None
+ )
+ _dict["questions"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in selected_articles (list)
+ _items = []
+ if self.selected_articles:
+ for _item_selected_articles in self.selected_articles:
+ _items.append(
+ _item_selected_articles.to_dict()
+ if _item_selected_articles is not None
+ else None
+ )
+ _dict["selectedArticles"] = _items
+ # override the default output from pydantic by calling `to_dict()` of sentiment
+ if self.sentiment:
+ _dict["sentiment"] = self.sentiment.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in countries (list)
+ _items = []
+ if self.countries:
+ for _item_countries in self.countries:
+ _items.append(
+ _item_countries.to_dict() if _item_countries is not None else None
+ )
+ _dict["countries"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in topics (list)
+ _items = []
+ if self.topics:
+ for _item_topics in self.topics:
+ _items.append(
+ _item_topics.to_dict() if _item_topics is not None else None
+ )
+ _dict["topics"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_topics (list)
+ _items = []
+ if self.top_topics:
+ for _item_top_topics in self.top_topics:
+ _items.append(
+ _item_top_topics.to_dict() if _item_top_topics is not None else None
+ )
+ _dict["topTopics"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in categories (list)
+ _items = []
+ if self.categories:
+ for _item_categories in self.categories:
+ _items.append(
+ _item_categories.to_dict() if _item_categories is not None else None
+ )
+ _dict["categories"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_categories (list)
+ _items = []
+ if self.top_categories:
+ for _item_top_categories in self.top_categories:
+ _items.append(
+ _item_top_categories.to_dict()
+ if _item_top_categories is not None
+ else None
+ )
+ _dict["topCategories"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in taxonomies (list)
+ _items = []
+ if self.taxonomies:
+ for _item_taxonomies in self.taxonomies:
+ _items.append(
+ _item_taxonomies.to_dict() if _item_taxonomies is not None else None
+ )
+ _dict["taxonomies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_taxonomies (list)
+ _items = []
+ if self.top_taxonomies:
+ for _item_top_taxonomies in self.top_taxonomies:
+ _items.append(
+ _item_top_taxonomies.to_dict()
+ if _item_top_taxonomies is not None
+ else None
+ )
+ _dict["topTaxonomies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in people (list)
+ _items = []
+ if self.people:
+ for _item_people in self.people:
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
+ _dict["people"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_people (list)
+ _items = []
+ if self.top_people:
+ for _item_top_people in self.top_people:
+ _items.append(
+ _item_top_people.to_dict() if _item_top_people is not None else None
+ )
+ _dict["topPeople"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in companies (list)
+ _items = []
+ if self.companies:
+ for _item_companies in self.companies:
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
+ _dict["companies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_companies (list)
+ _items = []
+ if self.top_companies:
+ for _item_top_companies in self.top_companies:
+ _items.append(
+ _item_top_companies.to_dict()
+ if _item_top_companies is not None
+ else None
+ )
+ _dict["topCompanies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in locations (list)
+ _items = []
+ if self.locations:
+ for _item_locations in self.locations:
+ _items.append(
+ _item_locations.to_dict() if _item_locations is not None else None
+ )
+ _dict["locations"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in top_locations (list)
+ _items = []
+ if self.top_locations:
+ for _item_top_locations in self.top_locations:
+ _items.append(
+ _item_top_locations.to_dict()
+ if _item_top_locations is not None
+ else None
+ )
+ _dict["topLocations"] = _items
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if initialized_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.initialized_at is None and "initialized_at" in self.model_fields_set:
+ _dict["initializedAt"] = None
+
+ # set to None if id (nullable) is None
+ # and model_fields_set contains the field
+ if self.id is None and "id" in self.model_fields_set:
+ _dict["id"] = None
+
+ # set to None if duplicate_of (nullable) is None
+ # and model_fields_set contains the field
+ if self.duplicate_of is None and "duplicate_of" in self.model_fields_set:
+ _dict["duplicateOf"] = None
+
+ # set to None if slug (nullable) is None
+ # and model_fields_set contains the field
+ if self.slug is None and "slug" in self.model_fields_set:
+ _dict["slug"] = None
+
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.summary is None and "summary" in self.model_fields_set:
+ _dict["summary"] = None
+
+ # set to None if short_summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.short_summary is None and "short_summary" in self.model_fields_set:
+ _dict["shortSummary"] = None
+
+ # set to None if summary_references (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.summary_references is None
+ and "summary_references" in self.model_fields_set
+ ):
+ _dict["summaryReferences"] = None
+
+ # set to None if image_source (nullable) is None
+ # and model_fields_set contains the field
+ if self.image_source is None and "image_source" in self.model_fields_set:
+ _dict["imageSource"] = None
+
+ # set to None if image_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.image_url is None and "image_url" in self.model_fields_set:
+ _dict["imageUrl"] = None
+
+ # set to None if key_points (nullable) is None
+ # and model_fields_set contains the field
+ if self.key_points is None and "key_points" in self.model_fields_set:
+ _dict["keyPoints"] = None
+
+ # set to None if questions (nullable) is None
+ # and model_fields_set contains the field
+ if self.questions is None and "questions" in self.model_fields_set:
+ _dict["questions"] = None
+
+ # set to None if unique_sources (nullable) is None
+ # and model_fields_set contains the field
+ if self.unique_sources is None and "unique_sources" in self.model_fields_set:
+ _dict["uniqueSources"] = None
+
+ # set to None if selected_articles (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.selected_articles is None
+ and "selected_articles" in self.model_fields_set
+ ):
+ _dict["selectedArticles"] = None
+
+ # set to None if sentiment (nullable) is None
+ # and model_fields_set contains the field
+ if self.sentiment is None and "sentiment" in self.model_fields_set:
+ _dict["sentiment"] = None
+
+ # set to None if unique_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.unique_count is None and "unique_count" in self.model_fields_set:
+ _dict["uniqueCount"] = None
+
+ # set to None if reprint_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.reprint_count is None and "reprint_count" in self.model_fields_set:
+ _dict["reprintCount"] = None
+
+ # set to None if total_count (nullable) is None
+ # and model_fields_set contains the field
+ if self.total_count is None and "total_count" in self.model_fields_set:
+ _dict["totalCount"] = None
+
+ # set to None if countries (nullable) is None
+ # and model_fields_set contains the field
+ if self.countries is None and "countries" in self.model_fields_set:
+ _dict["countries"] = None
+
+ # set to None if top_countries (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_countries is None and "top_countries" in self.model_fields_set:
+ _dict["topCountries"] = None
+
+ # set to None if topics (nullable) is None
+ # and model_fields_set contains the field
+ if self.topics is None and "topics" in self.model_fields_set:
+ _dict["topics"] = None
+
+ # set to None if top_topics (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_topics is None and "top_topics" in self.model_fields_set:
+ _dict["topTopics"] = None
+
+ # set to None if categories (nullable) is None
+ # and model_fields_set contains the field
+ if self.categories is None and "categories" in self.model_fields_set:
+ _dict["categories"] = None
+
+ # set to None if top_categories (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_categories is None and "top_categories" in self.model_fields_set:
+ _dict["topCategories"] = None
+
+ # set to None if taxonomies (nullable) is None
+ # and model_fields_set contains the field
+ if self.taxonomies is None and "taxonomies" in self.model_fields_set:
+ _dict["taxonomies"] = None
+
+ # set to None if top_taxonomies (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_taxonomies is None and "top_taxonomies" in self.model_fields_set:
+ _dict["topTaxonomies"] = None
+
+ # set to None if people (nullable) is None
+ # and model_fields_set contains the field
+ if self.people is None and "people" in self.model_fields_set:
+ _dict["people"] = None
+
+ # set to None if top_people (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_people is None and "top_people" in self.model_fields_set:
+ _dict["topPeople"] = None
+
+ # set to None if companies (nullable) is None
+ # and model_fields_set contains the field
+ if self.companies is None and "companies" in self.model_fields_set:
+ _dict["companies"] = None
+
+ # set to None if top_companies (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_companies is None and "top_companies" in self.model_fields_set:
+ _dict["topCompanies"] = None
+
+ # set to None if locations (nullable) is None
+ # and model_fields_set contains the field
+ if self.locations is None and "locations" in self.model_fields_set:
+ _dict["locations"] = None
+
+ # set to None if top_locations (nullable) is None
+ # and model_fields_set contains the field
+ if self.top_locations is None and "top_locations" in self.model_fields_set:
+ _dict["topLocations"] = None
+
+ # set to None if highlights (nullable) is None
+ # and model_fields_set contains the field
+ if self.highlights is None and "highlights" in self.model_fields_set:
+ _dict["highlights"] = None
+
+ # set to None if source_diversity (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.source_diversity is None
+ and "source_diversity" in self.model_fields_set
+ ):
+ _dict["sourceDiversity"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of NewsCluster from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "initializedAt": obj.get("initializedAt"),
+ "id": obj.get("id"),
+ "duplicateOf": obj.get("duplicateOf"),
+ "slug": obj.get("slug"),
+ "name": obj.get("name"),
+ "summary": obj.get("summary"),
+ "shortSummary": obj.get("shortSummary"),
+ "summaryReferences": obj.get("summaryReferences"),
+ "imageSource": SourceHolder.from_dict(obj["imageSource"])
+ if obj.get("imageSource") is not None
+ else None,
+ "imageUrl": obj.get("imageUrl"),
+ "keyPoints": [KeyPoint.from_dict(_item) for _item in obj["keyPoints"]]
+ if obj.get("keyPoints") is not None
+ else None,
+ "questions": [Question.from_dict(_item) for _item in obj["questions"]]
+ if obj.get("questions") is not None
+ else None,
+ "uniqueSources": obj.get("uniqueSources"),
+ "selectedArticles": [
+ NewsClusterArticle.from_dict(_item)
+ for _item in obj["selectedArticles"]
+ ]
+ if obj.get("selectedArticles") is not None
+ else None,
+ "sentiment": SentimentHolder.from_dict(obj["sentiment"])
+ if obj.get("sentiment") is not None
+ else None,
+ "uniqueCount": obj.get("uniqueCount"),
+ "reprintCount": obj.get("reprintCount"),
+ "totalCount": obj.get("totalCount"),
+ "countries": [
+ RecordStatHolder.from_dict(_item) for _item in obj["countries"]
+ ]
+ if obj.get("countries") is not None
+ else None,
+ "topCountries": obj.get("topCountries"),
+ "topics": [RecordStatHolder.from_dict(_item) for _item in obj["topics"]]
+ if obj.get("topics") is not None
+ else None,
+ "topTopics": [
+ TopicHolder.from_dict(_item) for _item in obj["topTopics"]
+ ]
+ if obj.get("topTopics") is not None
+ else None,
+ "categories": [
+ RecordStatHolder.from_dict(_item) for _item in obj["categories"]
+ ]
+ if obj.get("categories") is not None
+ else None,
+ "topCategories": [
+ CategoryHolder.from_dict(_item) for _item in obj["topCategories"]
+ ]
+ if obj.get("topCategories") is not None
+ else None,
+ "taxonomies": [
+ RecordStatHolder.from_dict(_item) for _item in obj["taxonomies"]
+ ]
+ if obj.get("taxonomies") is not None
+ else None,
+ "topTaxonomies": [
+ CategoryHolder.from_dict(_item) for _item in obj["topTaxonomies"]
+ ]
+ if obj.get("topTaxonomies") is not None
+ else None,
+ "people": [PersonCount.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "topPeople": [
+ PersonHolder.from_dict(_item) for _item in obj["topPeople"]
+ ]
+ if obj.get("topPeople") is not None
+ else None,
+ "companies": [
+ CompanyCount.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "topCompanies": [
+ CompanyHolder.from_dict(_item) for _item in obj["topCompanies"]
+ ]
+ if obj.get("topCompanies") is not None
+ else None,
+ "locations": [
+ LocationCount.from_dict(_item) for _item in obj["locations"]
+ ]
+ if obj.get("locations") is not None
+ else None,
+ "topLocations": [
+ LocationHolder.from_dict(_item) for _item in obj["topLocations"]
+ ]
+ if obj.get("topLocations") is not None
+ else None,
+ "highlights": obj.get("highlights"),
+ "sourceDiversity": obj.get("sourceDiversity"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/news_cluster_article.py b/src/perigon/models/news_cluster_article.py
new file mode 100644
index 0000000..2d8cfc4
--- /dev/null
+++ b/src/perigon/models/news_cluster_article.py
@@ -0,0 +1,752 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set, Union
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictFloat,
+ StrictInt,
+ StrictStr,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.category_holder import CategoryHolder
+from perigon.models.category_with_score_holder import CategoryWithScoreHolder
+from perigon.models.company_holder import CompanyHolder
+from perigon.models.entity_holder import EntityHolder
+from perigon.models.event_type_holder import EventTypeHolder
+from perigon.models.id_name_holder import IdNameHolder
+from perigon.models.journalist import Journalist
+from perigon.models.keyword_holder import KeywordHolder
+from perigon.models.label_holder import LabelHolder
+from perigon.models.location_holder import LocationHolder
+from perigon.models.person_holder import PersonHolder
+from perigon.models.place import Place
+from perigon.models.sentiment_holder import SentimentHolder
+from perigon.models.source_holder import SourceHolder
+from perigon.models.topic_holder import TopicHolder
+
+
+class NewsClusterArticle(BaseModel):
+ """
+ Article embedded in a story cluster, without the parent cluster.
+ """
+
+ url: Optional[StrictStr] = Field(
+ default=None, description="Canonical URL of the article."
+ )
+ authors_byline: Optional[StrictStr] = Field(
+ default=None,
+ description="Author byline as published by the source.",
+ alias="authorsByline",
+ )
+ article_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Unique Perigon identifier for the article.",
+ alias="articleId",
+ )
+ cluster_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier of the story cluster containing the article, when assigned.",
+ alias="clusterId",
+ )
+ source: Optional[SourceHolder] = None
+ image_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Primary image URL associated with the article.",
+ alias="imageUrl",
+ )
+ country: Optional[StrictStr] = Field(
+ default=None,
+ description="Two-character country code associated with the article.",
+ )
+ language: Optional[StrictStr] = Field(
+ default=None, description="ISO 639 two-letter language code for the article."
+ )
+ pub_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was published, in ISO 8601 format.",
+ alias="pubDate",
+ )
+ add_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was added to Perigon, in ISO 8601 format.",
+ alias="addDate",
+ )
+ refresh_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the article was last refreshed by Perigon, in ISO 8601 format.",
+ alias="refreshDate",
+ )
+ score: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None,
+ description="Search relevance score for the article, when applicable.",
+ )
+ title: Optional[StrictStr] = Field(default=None, description="Article headline.")
+ description: Optional[StrictStr] = Field(
+ default=None, description="Article description or publisher-provided excerpt."
+ )
+ content: Optional[StrictStr] = Field(
+ default=None, description="Full article body available to Perigon."
+ )
+ en_content_word_count: Optional[StrictInt] = Field(
+ default=None,
+ description="Word count of the English article content, when available.",
+ alias="enContentWordCount",
+ )
+ medium: Optional[StrictStr] = Field(
+ default=None, description="Primary medium type, such as Article or Video."
+ )
+ links: Optional[List[StrictStr]] = Field(
+ default=None, description="URLs linked from the article content."
+ )
+ labels: Optional[List[LabelHolder]] = Field(
+ default=None, description="Editorial labels assigned to the article."
+ )
+ event_types: Optional[List[EventTypeHolder]] = Field(
+ default=None,
+ description="Event types detected in the article.",
+ alias="eventTypes",
+ )
+ matched_authors: Optional[List[IdNameHolder]] = Field(
+ default=None,
+ description="Known journalist records matched to the article byline.",
+ alias="matchedAuthors",
+ )
+ claim: Optional[StrictStr] = Field(
+ default=None,
+ description="Claim identified in fact-check content, when applicable.",
+ )
+ verdict: Optional[StrictStr] = Field(
+ default=None,
+ description="Verdict identified in fact-check content, when applicable.",
+ )
+ keywords: Optional[List[KeywordHolder]] = Field(
+ default=None,
+ description="Keywords extracted from the article with their relevance weights.",
+ )
+ topics: Optional[List[TopicHolder]] = Field(
+ default=None, description="Topics assigned to the article."
+ )
+ categories: Optional[List[CategoryHolder]] = Field(
+ default=None, description="Broad content categories assigned to the article."
+ )
+ taxonomies: Optional[List[CategoryWithScoreHolder]] = Field(
+ default=None,
+ description="Google Content Categories assigned to the article with confidence scores.",
+ )
+ entities: Optional[List[EntityHolder]] = Field(
+ default=None, description="Named entities extracted from the article."
+ )
+ companies: Optional[List[CompanyHolder]] = Field(
+ default=None, description="Companies identified in the article."
+ )
+ sentiment: Optional[SentimentHolder] = None
+ summary: Optional[StrictStr] = Field(
+ default=None, description="Generated summary of the article, when available."
+ )
+ short_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Short generated summary of the article, when available.",
+ alias="shortSummary",
+ )
+ translation: Optional[StrictStr] = Field(
+ default=None, description="Translated article content, when available."
+ )
+ translated_title: Optional[StrictStr] = Field(
+ default=None,
+ description="English translation of the article title, when available.",
+ alias="translatedTitle",
+ )
+ translated_description: Optional[StrictStr] = Field(
+ default=None,
+ description="English translation of the article description, when available.",
+ alias="translatedDescription",
+ )
+ translated_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="English translation of the article summary, when available.",
+ alias="translatedSummary",
+ )
+ locations: Optional[List[LocationHolder]] = Field(
+ default=None, description="Locations identified as central to the article."
+ )
+ reprint: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the article is a reprint of previously published content.",
+ )
+ reprint_group_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Identifier grouping the original article with its known reprints.",
+ alias="reprintGroupId",
+ )
+ places: Optional[List[Place]] = Field(
+ default=None,
+ description="Structured geographic places extracted from the article.",
+ )
+ people: Optional[List[PersonHolder]] = Field(
+ default=None, description="Known people identified in the article."
+ )
+ journalists: Optional[List[Journalist]] = Field(
+ default=None,
+ description="Expanded journalist details, when requested and available.",
+ )
+ highlights: Optional[Dict[str, List[StrictStr]]] = Field(
+ default=None, description="Matched text fragments grouped by article field."
+ )
+ __properties: ClassVar[List[str]] = [
+ "url",
+ "authorsByline",
+ "articleId",
+ "clusterId",
+ "source",
+ "imageUrl",
+ "country",
+ "language",
+ "pubDate",
+ "addDate",
+ "refreshDate",
+ "score",
+ "title",
+ "description",
+ "content",
+ "enContentWordCount",
+ "medium",
+ "links",
+ "labels",
+ "eventTypes",
+ "matchedAuthors",
+ "claim",
+ "verdict",
+ "keywords",
+ "topics",
+ "categories",
+ "taxonomies",
+ "entities",
+ "companies",
+ "sentiment",
+ "summary",
+ "shortSummary",
+ "translation",
+ "translatedTitle",
+ "translatedDescription",
+ "translatedSummary",
+ "locations",
+ "reprint",
+ "reprintGroupId",
+ "places",
+ "people",
+ "journalists",
+ "highlights",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of NewsClusterArticle from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of source
+ if self.source:
+ _dict["source"] = self.source.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in labels (list)
+ _items = []
+ if self.labels:
+ for _item_labels in self.labels:
+ _items.append(
+ _item_labels.to_dict() if _item_labels is not None else None
+ )
+ _dict["labels"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in event_types (list)
+ _items = []
+ if self.event_types:
+ for _item_event_types in self.event_types:
+ _items.append(
+ _item_event_types.to_dict()
+ if _item_event_types is not None
+ else None
+ )
+ _dict["eventTypes"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in matched_authors (list)
+ _items = []
+ if self.matched_authors:
+ for _item_matched_authors in self.matched_authors:
+ _items.append(
+ _item_matched_authors.to_dict()
+ if _item_matched_authors is not None
+ else None
+ )
+ _dict["matchedAuthors"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in keywords (list)
+ _items = []
+ if self.keywords:
+ for _item_keywords in self.keywords:
+ _items.append(
+ _item_keywords.to_dict() if _item_keywords is not None else None
+ )
+ _dict["keywords"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in topics (list)
+ _items = []
+ if self.topics:
+ for _item_topics in self.topics:
+ _items.append(
+ _item_topics.to_dict() if _item_topics is not None else None
+ )
+ _dict["topics"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in categories (list)
+ _items = []
+ if self.categories:
+ for _item_categories in self.categories:
+ _items.append(
+ _item_categories.to_dict() if _item_categories is not None else None
+ )
+ _dict["categories"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in taxonomies (list)
+ _items = []
+ if self.taxonomies:
+ for _item_taxonomies in self.taxonomies:
+ _items.append(
+ _item_taxonomies.to_dict() if _item_taxonomies is not None else None
+ )
+ _dict["taxonomies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in entities (list)
+ _items = []
+ if self.entities:
+ for _item_entities in self.entities:
+ _items.append(
+ _item_entities.to_dict() if _item_entities is not None else None
+ )
+ _dict["entities"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in companies (list)
+ _items = []
+ if self.companies:
+ for _item_companies in self.companies:
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
+ _dict["companies"] = _items
+ # override the default output from pydantic by calling `to_dict()` of sentiment
+ if self.sentiment:
+ _dict["sentiment"] = self.sentiment.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in locations (list)
+ _items = []
+ if self.locations:
+ for _item_locations in self.locations:
+ _items.append(
+ _item_locations.to_dict() if _item_locations is not None else None
+ )
+ _dict["locations"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in places (list)
+ _items = []
+ if self.places:
+ for _item_places in self.places:
+ _items.append(
+ _item_places.to_dict() if _item_places is not None else None
+ )
+ _dict["places"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in people (list)
+ _items = []
+ if self.people:
+ for _item_people in self.people:
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
+ _dict["people"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in journalists (list)
+ _items = []
+ if self.journalists:
+ for _item_journalists in self.journalists:
+ _items.append(
+ _item_journalists.to_dict()
+ if _item_journalists is not None
+ else None
+ )
+ _dict["journalists"] = _items
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if authors_byline (nullable) is None
+ # and model_fields_set contains the field
+ if self.authors_byline is None and "authors_byline" in self.model_fields_set:
+ _dict["authorsByline"] = None
+
+ # set to None if article_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.article_id is None and "article_id" in self.model_fields_set:
+ _dict["articleId"] = None
+
+ # set to None if cluster_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.cluster_id is None and "cluster_id" in self.model_fields_set:
+ _dict["clusterId"] = None
+
+ # set to None if source (nullable) is None
+ # and model_fields_set contains the field
+ if self.source is None and "source" in self.model_fields_set:
+ _dict["source"] = None
+
+ # set to None if image_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.image_url is None and "image_url" in self.model_fields_set:
+ _dict["imageUrl"] = None
+
+ # set to None if country (nullable) is None
+ # and model_fields_set contains the field
+ if self.country is None and "country" in self.model_fields_set:
+ _dict["country"] = None
+
+ # set to None if language (nullable) is None
+ # and model_fields_set contains the field
+ if self.language is None and "language" in self.model_fields_set:
+ _dict["language"] = None
+
+ # set to None if pub_date (nullable) is None
+ # and model_fields_set contains the field
+ if self.pub_date is None and "pub_date" in self.model_fields_set:
+ _dict["pubDate"] = None
+
+ # set to None if add_date (nullable) is None
+ # and model_fields_set contains the field
+ if self.add_date is None and "add_date" in self.model_fields_set:
+ _dict["addDate"] = None
+
+ # set to None if refresh_date (nullable) is None
+ # and model_fields_set contains the field
+ if self.refresh_date is None and "refresh_date" in self.model_fields_set:
+ _dict["refreshDate"] = None
+
+ # set to None if score (nullable) is None
+ # and model_fields_set contains the field
+ if self.score is None and "score" in self.model_fields_set:
+ _dict["score"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if content (nullable) is None
+ # and model_fields_set contains the field
+ if self.content is None and "content" in self.model_fields_set:
+ _dict["content"] = None
+
+ # set to None if en_content_word_count (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.en_content_word_count is None
+ and "en_content_word_count" in self.model_fields_set
+ ):
+ _dict["enContentWordCount"] = None
+
+ # set to None if medium (nullable) is None
+ # and model_fields_set contains the field
+ if self.medium is None and "medium" in self.model_fields_set:
+ _dict["medium"] = None
+
+ # set to None if links (nullable) is None
+ # and model_fields_set contains the field
+ if self.links is None and "links" in self.model_fields_set:
+ _dict["links"] = None
+
+ # set to None if labels (nullable) is None
+ # and model_fields_set contains the field
+ if self.labels is None and "labels" in self.model_fields_set:
+ _dict["labels"] = None
+
+ # set to None if event_types (nullable) is None
+ # and model_fields_set contains the field
+ if self.event_types is None and "event_types" in self.model_fields_set:
+ _dict["eventTypes"] = None
+
+ # set to None if matched_authors (nullable) is None
+ # and model_fields_set contains the field
+ if self.matched_authors is None and "matched_authors" in self.model_fields_set:
+ _dict["matchedAuthors"] = None
+
+ # set to None if claim (nullable) is None
+ # and model_fields_set contains the field
+ if self.claim is None and "claim" in self.model_fields_set:
+ _dict["claim"] = None
+
+ # set to None if verdict (nullable) is None
+ # and model_fields_set contains the field
+ if self.verdict is None and "verdict" in self.model_fields_set:
+ _dict["verdict"] = None
+
+ # set to None if keywords (nullable) is None
+ # and model_fields_set contains the field
+ if self.keywords is None and "keywords" in self.model_fields_set:
+ _dict["keywords"] = None
+
+ # set to None if topics (nullable) is None
+ # and model_fields_set contains the field
+ if self.topics is None and "topics" in self.model_fields_set:
+ _dict["topics"] = None
+
+ # set to None if categories (nullable) is None
+ # and model_fields_set contains the field
+ if self.categories is None and "categories" in self.model_fields_set:
+ _dict["categories"] = None
+
+ # set to None if taxonomies (nullable) is None
+ # and model_fields_set contains the field
+ if self.taxonomies is None and "taxonomies" in self.model_fields_set:
+ _dict["taxonomies"] = None
+
+ # set to None if entities (nullable) is None
+ # and model_fields_set contains the field
+ if self.entities is None and "entities" in self.model_fields_set:
+ _dict["entities"] = None
+
+ # set to None if companies (nullable) is None
+ # and model_fields_set contains the field
+ if self.companies is None and "companies" in self.model_fields_set:
+ _dict["companies"] = None
+
+ # set to None if sentiment (nullable) is None
+ # and model_fields_set contains the field
+ if self.sentiment is None and "sentiment" in self.model_fields_set:
+ _dict["sentiment"] = None
+
+ # set to None if summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.summary is None and "summary" in self.model_fields_set:
+ _dict["summary"] = None
+
+ # set to None if short_summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.short_summary is None and "short_summary" in self.model_fields_set:
+ _dict["shortSummary"] = None
+
+ # set to None if translation (nullable) is None
+ # and model_fields_set contains the field
+ if self.translation is None and "translation" in self.model_fields_set:
+ _dict["translation"] = None
+
+ # set to None if translated_title (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.translated_title is None
+ and "translated_title" in self.model_fields_set
+ ):
+ _dict["translatedTitle"] = None
+
+ # set to None if translated_description (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.translated_description is None
+ and "translated_description" in self.model_fields_set
+ ):
+ _dict["translatedDescription"] = None
+
+ # set to None if translated_summary (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.translated_summary is None
+ and "translated_summary" in self.model_fields_set
+ ):
+ _dict["translatedSummary"] = None
+
+ # set to None if locations (nullable) is None
+ # and model_fields_set contains the field
+ if self.locations is None and "locations" in self.model_fields_set:
+ _dict["locations"] = None
+
+ # set to None if reprint (nullable) is None
+ # and model_fields_set contains the field
+ if self.reprint is None and "reprint" in self.model_fields_set:
+ _dict["reprint"] = None
+
+ # set to None if reprint_group_id (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.reprint_group_id is None
+ and "reprint_group_id" in self.model_fields_set
+ ):
+ _dict["reprintGroupId"] = None
+
+ # set to None if places (nullable) is None
+ # and model_fields_set contains the field
+ if self.places is None and "places" in self.model_fields_set:
+ _dict["places"] = None
+
+ # set to None if people (nullable) is None
+ # and model_fields_set contains the field
+ if self.people is None and "people" in self.model_fields_set:
+ _dict["people"] = None
+
+ # set to None if journalists (nullable) is None
+ # and model_fields_set contains the field
+ if self.journalists is None and "journalists" in self.model_fields_set:
+ _dict["journalists"] = None
+
+ # set to None if highlights (nullable) is None
+ # and model_fields_set contains the field
+ if self.highlights is None and "highlights" in self.model_fields_set:
+ _dict["highlights"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of NewsClusterArticle from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "url": obj.get("url"),
+ "authorsByline": obj.get("authorsByline"),
+ "articleId": obj.get("articleId"),
+ "clusterId": obj.get("clusterId"),
+ "source": SourceHolder.from_dict(obj["source"])
+ if obj.get("source") is not None
+ else None,
+ "imageUrl": obj.get("imageUrl"),
+ "country": obj.get("country"),
+ "language": obj.get("language"),
+ "pubDate": obj.get("pubDate"),
+ "addDate": obj.get("addDate"),
+ "refreshDate": obj.get("refreshDate"),
+ "score": obj.get("score"),
+ "title": obj.get("title"),
+ "description": obj.get("description"),
+ "content": obj.get("content"),
+ "enContentWordCount": obj.get("enContentWordCount"),
+ "medium": obj.get("medium"),
+ "links": obj.get("links"),
+ "labels": [LabelHolder.from_dict(_item) for _item in obj["labels"]]
+ if obj.get("labels") is not None
+ else None,
+ "eventTypes": [
+ EventTypeHolder.from_dict(_item) for _item in obj["eventTypes"]
+ ]
+ if obj.get("eventTypes") is not None
+ else None,
+ "matchedAuthors": [
+ IdNameHolder.from_dict(_item) for _item in obj["matchedAuthors"]
+ ]
+ if obj.get("matchedAuthors") is not None
+ else None,
+ "claim": obj.get("claim"),
+ "verdict": obj.get("verdict"),
+ "keywords": [
+ KeywordHolder.from_dict(_item) for _item in obj["keywords"]
+ ]
+ if obj.get("keywords") is not None
+ else None,
+ "topics": [TopicHolder.from_dict(_item) for _item in obj["topics"]]
+ if obj.get("topics") is not None
+ else None,
+ "categories": [
+ CategoryHolder.from_dict(_item) for _item in obj["categories"]
+ ]
+ if obj.get("categories") is not None
+ else None,
+ "taxonomies": [
+ CategoryWithScoreHolder.from_dict(_item)
+ for _item in obj["taxonomies"]
+ ]
+ if obj.get("taxonomies") is not None
+ else None,
+ "entities": [EntityHolder.from_dict(_item) for _item in obj["entities"]]
+ if obj.get("entities") is not None
+ else None,
+ "companies": [
+ CompanyHolder.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "sentiment": SentimentHolder.from_dict(obj["sentiment"])
+ if obj.get("sentiment") is not None
+ else None,
+ "summary": obj.get("summary"),
+ "shortSummary": obj.get("shortSummary"),
+ "translation": obj.get("translation"),
+ "translatedTitle": obj.get("translatedTitle"),
+ "translatedDescription": obj.get("translatedDescription"),
+ "translatedSummary": obj.get("translatedSummary"),
+ "locations": [
+ LocationHolder.from_dict(_item) for _item in obj["locations"]
+ ]
+ if obj.get("locations") is not None
+ else None,
+ "reprint": obj.get("reprint"),
+ "reprintGroupId": obj.get("reprintGroupId"),
+ "places": [Place.from_dict(_item) for _item in obj["places"]]
+ if obj.get("places") is not None
+ else None,
+ "people": [PersonHolder.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "journalists": [
+ Journalist.from_dict(_item) for _item in obj["journalists"]
+ ]
+ if obj.get("journalists") is not None
+ else None,
+ "highlights": obj.get("highlights"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/patch_source_group_params.py b/src/perigon/models/patch_source_group_params.py
similarity index 95%
rename from perigon/models/patch_source_group_params.py
rename to src/perigon/models/patch_source_group_params.py
index 48ca1b8..166c9b8 100644
--- a/perigon/models/patch_source_group_params.py
+++ b/src/perigon/models/patch_source_group_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
class PatchSourceGroupParams(BaseModel):
"""
PatchSourceGroupParams
- """ # noqa: E501
+ """
name: Optional[StrictStr] = Field(
default=None,
@@ -52,7 +52,8 @@ class PatchSourceGroupParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -63,8 +64,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/people_search_result.py b/src/perigon/models/people_search_result.py
similarity index 85%
rename from perigon/models/people_search_result.py
rename to src/perigon/models/people_search_result.py
index 0d1b12c..f32e27c 100644
--- a/perigon/models/people_search_result.py
+++ b/src/perigon/models/people_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.person import Person
@@ -29,7 +29,7 @@
class PeopleSearchResult(BaseModel):
"""
Person search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class PeopleSearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [Person.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [Person.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/person.py b/src/perigon/models/person.py
similarity index 80%
rename from perigon/models/person.py
rename to src/perigon/models/person.py
index 847bb28..ee783aa 100644
--- a/perigon/models/person.py
+++ b/src/perigon/models/person.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.image_holder import ImageHolder
@@ -33,7 +33,7 @@
class Person(BaseModel):
"""
Person
- """ # noqa: E501
+ """
wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
created_at: Optional[StrictStr] = Field(default=None, alias="createdAt")
@@ -73,7 +73,8 @@ class Person(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -84,8 +85,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -122,22 +122,27 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.occupation:
for _item_occupation in self.occupation:
- if _item_occupation:
- _items.append(_item_occupation.to_dict())
+ _items.append(
+ _item_occupation.to_dict() if _item_occupation is not None else None
+ )
_dict["occupation"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in position (list)
_items = []
if self.position:
for _item_position in self.position:
- if _item_position:
- _items.append(_item_position.to_dict())
+ _items.append(
+ _item_position.to_dict() if _item_position is not None else None
+ )
_dict["position"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in political_party (list)
_items = []
if self.political_party:
for _item_political_party in self.political_party:
- if _item_political_party:
- _items.append(_item_political_party.to_dict())
+ _items.append(
+ _item_political_party.to_dict()
+ if _item_political_party is not None
+ else None
+ )
_dict["politicalParty"] = _items
# override the default output from pydantic by calling `to_dict()` of image
if self.image:
@@ -229,52 +234,36 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"createdAt": obj.get("createdAt"),
"updatedAt": obj.get("updatedAt"),
"name": obj.get("name"),
- "gender": (
- WikidataLabelHolder.from_dict(obj["gender"])
- if obj.get("gender") is not None
- else None
- ),
- "dateOfBirth": (
- WikidataDateHolder.from_dict(obj["dateOfBirth"])
- if obj.get("dateOfBirth") is not None
- else None
- ),
- "dateOfDeath": (
- WikidataDateHolder.from_dict(obj["dateOfDeath"])
- if obj.get("dateOfDeath") is not None
- else None
- ),
+ "gender": WikidataLabelHolder.from_dict(obj["gender"])
+ if obj.get("gender") is not None
+ else None,
+ "dateOfBirth": WikidataDateHolder.from_dict(obj["dateOfBirth"])
+ if obj.get("dateOfBirth") is not None
+ else None,
+ "dateOfDeath": WikidataDateHolder.from_dict(obj["dateOfDeath"])
+ if obj.get("dateOfDeath") is not None
+ else None,
"description": obj.get("description"),
"aliases": obj.get("aliases"),
- "occupation": (
- [
- WikidataLabelHolder.from_dict(_item)
- for _item in obj["occupation"]
- ]
- if obj.get("occupation") is not None
- else None
- ),
- "position": (
- [
- WikidataPositionHolder.from_dict(_item)
- for _item in obj["position"]
- ]
- if obj.get("position") is not None
- else None
- ),
- "politicalParty": (
- [
- WikidataPoliticalPartyHolder.from_dict(_item)
- for _item in obj["politicalParty"]
- ]
- if obj.get("politicalParty") is not None
- else None
- ),
- "image": (
- ImageHolder.from_dict(obj["image"])
- if obj.get("image") is not None
- else None
- ),
+ "occupation": [
+ WikidataLabelHolder.from_dict(_item) for _item in obj["occupation"]
+ ]
+ if obj.get("occupation") is not None
+ else None,
+ "position": [
+ WikidataPositionHolder.from_dict(_item) for _item in obj["position"]
+ ]
+ if obj.get("position") is not None
+ else None,
+ "politicalParty": [
+ WikidataPoliticalPartyHolder.from_dict(_item)
+ for _item in obj["politicalParty"]
+ ]
+ if obj.get("politicalParty") is not None
+ else None,
+ "image": ImageHolder.from_dict(obj["image"])
+ if obj.get("image") is not None
+ else None,
"abstract": obj.get("abstract"),
}
)
diff --git a/perigon/models/person_count.py b/src/perigon/models/person_count.py
similarity index 83%
rename from perigon/models/person_count.py
rename to src/perigon/models/person_count.py
index a98aeb5..ca84c1d 100644
--- a/perigon/models/person_count.py
+++ b/src/perigon/models/person_count.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,21 +20,29 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class PersonCount(BaseModel):
"""
- PersonCount
- """ # noqa: E501
+ People associated with the story and their article counts.
+ """
- wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
- name: Optional[StrictStr] = None
- count: Optional[StrictInt] = None
+ wikidata_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Wikidata identifier for the person.",
+ alias="wikidataId",
+ )
+ name: Optional[StrictStr] = Field(default=None, description="Person name.")
+ count: Optional[StrictInt] = Field(
+ default=None, description="Number of story articles mentioning the person."
+ )
__properties: ClassVar[List[str]] = ["wikidataId", "name", "count"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/person_holder.py b/src/perigon/models/person_holder.py
similarity index 85%
rename from perigon/models/person_holder.py
rename to src/perigon/models/person_holder.py
index fb1b542..912db5e 100644
--- a/perigon/models/person_holder.py
+++ b/src/perigon/models/person_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,20 +20,26 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class PersonHolder(BaseModel):
"""
- PersonHolder
- """ # noqa: E501
+ People most strongly associated with the story.
+ """
- wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
- name: Optional[StrictStr] = None
+ wikidata_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Wikidata identifier for the person.",
+ alias="wikidataId",
+ )
+ name: Optional[StrictStr] = Field(default=None, description="Person name.")
__properties: ClassVar[List[str]] = ["wikidataId", "name"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/place.py b/src/perigon/models/place.py
similarity index 75%
rename from perigon/models/place.py
rename to src/perigon/models/place.py
index 68acc68..88aa60b 100644
--- a/perigon/models/place.py
+++ b/src/perigon/models/place.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.coordinate import Coordinate
@@ -28,23 +28,57 @@
class Place(BaseModel):
"""
- Place
- """ # noqa: E501
-
- osm_id: Optional[StrictStr] = Field(default=None, alias="osmId")
- road: Optional[StrictStr] = None
- quarter: Optional[StrictStr] = None
- suburb: Optional[StrictStr] = None
- city: Optional[StrictStr] = None
- town: Optional[StrictStr] = None
- county: Optional[StrictStr] = None
- state_district: Optional[StrictStr] = Field(default=None, alias="stateDistrict")
- state: Optional[StrictStr] = None
- postcode: Optional[StrictStr] = None
- country: Optional[StrictStr] = None
- country_code: Optional[StrictStr] = Field(default=None, alias="countryCode")
- amenity: Optional[StrictStr] = None
- neighbourhood: Optional[StrictStr] = None
+ Structured geographic places extracted from the article.
+ """
+
+ osm_id: Optional[StrictStr] = Field(
+ default=None,
+ description="OpenStreetMap identifier for the place.",
+ alias="osmId",
+ )
+ road: Optional[StrictStr] = Field(
+ default=None, description="Road associated with the place."
+ )
+ quarter: Optional[StrictStr] = Field(
+ default=None, description="Quarter associated with the place."
+ )
+ suburb: Optional[StrictStr] = Field(
+ default=None, description="Suburb associated with the place."
+ )
+ city: Optional[StrictStr] = Field(
+ default=None, description="City associated with the place."
+ )
+ town: Optional[StrictStr] = Field(
+ default=None, description="Town associated with the place."
+ )
+ county: Optional[StrictStr] = Field(
+ default=None, description="County associated with the place."
+ )
+ state_district: Optional[StrictStr] = Field(
+ default=None,
+ description="State district associated with the place.",
+ alias="stateDistrict",
+ )
+ state: Optional[StrictStr] = Field(
+ default=None, description="State or region associated with the place."
+ )
+ postcode: Optional[StrictStr] = Field(
+ default=None, description="Postal code associated with the place."
+ )
+ country: Optional[StrictStr] = Field(
+ default=None, description="Country associated with the place."
+ )
+ country_code: Optional[StrictStr] = Field(
+ default=None,
+ description="Two-character country code for the place.",
+ alias="countryCode",
+ )
+ amenity: Optional[StrictStr] = Field(
+ default=None, description="Named amenity associated with the place."
+ )
+ neighbourhood: Optional[StrictStr] = Field(
+ default=None, description="Neighborhood associated with the place."
+ )
coordinates: Optional[Coordinate] = None
__properties: ClassVar[List[str]] = [
"osmId",
@@ -65,7 +99,8 @@ class Place(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -76,8 +111,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -206,11 +240,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"countryCode": obj.get("countryCode"),
"amenity": obj.get("amenity"),
"neighbourhood": obj.get("neighbourhood"),
- "coordinates": (
- Coordinate.from_dict(obj["coordinates"])
- if obj.get("coordinates") is not None
- else None
- ),
+ "coordinates": Coordinate.from_dict(obj["coordinates"])
+ if obj.get("coordinates") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/query_search_result.py b/src/perigon/models/query_search_result.py
similarity index 85%
rename from perigon/models/query_search_result.py
rename to src/perigon/models/query_search_result.py
index c2f8c1a..6f3d068 100644
--- a/perigon/models/query_search_result.py
+++ b/src/perigon/models/query_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.article import Article
@@ -29,7 +29,7 @@
class QuerySearchResult(BaseModel):
"""
QuerySearchResult
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class QuerySearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "articles"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.articles:
for _item_articles in self.articles:
- if _item_articles:
- _items.append(_item_articles.to_dict())
+ _items.append(
+ _item_articles.to_dict() if _item_articles is not None else None
+ )
_dict["articles"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "articles": (
- [Article.from_dict(_item) for _item in obj["articles"]]
- if obj.get("articles") is not None
- else None
- ),
+ "articles": [Article.from_dict(_item) for _item in obj["articles"]]
+ if obj.get("articles") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/question.py b/src/perigon/models/question.py
similarity index 82%
rename from perigon/models/question.py
rename to src/perigon/models/question.py
index 68bcb4d..43495cd 100644
--- a/perigon/models/question.py
+++ b/src/perigon/models/question.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,22 +19,30 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class Question(BaseModel):
"""
- Question
- """ # noqa: E501
+ Questions and answers generated from the story content.
+ """
- question: Optional[StrictStr] = None
- answer: Optional[StrictStr] = None
- references: Optional[List[StrictStr]] = None
+ question: Optional[StrictStr] = Field(
+ default=None, description="Question generated from the story content."
+ )
+ answer: Optional[StrictStr] = Field(
+ default=None, description="Answer generated from the story content."
+ )
+ references: Optional[List[StrictStr]] = Field(
+ default=None, description="Article IDs supporting the answer."
+ )
__properties: ClassVar[List[str]] = ["question", "answer", "references"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/record_stat_holder.py b/src/perigon/models/record_stat_holder.py
similarity index 82%
rename from perigon/models/record_stat_holder.py
rename to src/perigon/models/record_stat_holder.py
index 25c11bb..b6e66e1 100644
--- a/perigon/models/record_stat_holder.py
+++ b/src/perigon/models/record_stat_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class RecordStatHolder(BaseModel):
"""
- RecordStatHolder
- """ # noqa: E501
+ Google Content Categories associated with the story and their article counts.
+ """
- name: Optional[StrictStr] = None
- count: Optional[StrictInt] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Name of the aggregated value."
+ )
+ count: Optional[StrictInt] = Field(
+ default=None, description="Number of matching articles for the value."
+ )
__properties: ClassVar[List[str]] = ["name", "count"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/schedule_interval.py b/src/perigon/models/schedule_interval.py
new file mode 100644
index 0000000..e5328b4
--- /dev/null
+++ b/src/perigon/models/schedule_interval.py
@@ -0,0 +1,173 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Annotated, Self
+
+
+class ScheduleInterval(BaseModel):
+ """
+ Times when the monitor runs. Must contain between 1 and 24 intervals.
+ """
+
+ hour: Optional[Annotated[int, Field(le=23, strict=True, ge=0)]] = Field(
+ default=None, description="Hour when the monitor runs, from 0 through 23."
+ )
+ minute: Optional[Annotated[int, Field(le=59, strict=True, ge=0)]] = Field(
+ default=None, description="Minute when the monitor runs, from 0 through 59."
+ )
+ days: Optional[Annotated[List[StrictStr], Field(min_length=1, max_length=7)]] = (
+ Field(
+ default=None,
+ description="Days when a WEEKLY schedule runs. Requires 1 through 7 values from MONDAY through SUNDAY.",
+ )
+ )
+ days_of_month: Optional[
+ Annotated[
+ List[Annotated[int, Field(le=31, strict=True, ge=1)]],
+ Field(min_length=0, max_length=31),
+ ]
+ ] = Field(
+ default=None,
+ description="Days of the month when a MONTHLY schedule runs. Required for MONTHLY schedules; each value must be from 1 through 31.",
+ alias="daysOfMonth",
+ )
+ schedule_type: StrictStr = Field(
+ description="Schedule recurrence type. Accepts WEEKLY or MONTHLY and defaults to WEEKLY.",
+ alias="scheduleType",
+ )
+ __properties: ClassVar[List[str]] = [
+ "hour",
+ "minute",
+ "days",
+ "daysOfMonth",
+ "scheduleType",
+ ]
+
+ @field_validator("days")
+ def days_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ for i in value:
+ if i not in set(
+ [
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+ "SUNDAY",
+ ]
+ ):
+ raise ValueError(
+ "each list item must be one of ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY')"
+ )
+ return value
+
+ @field_validator("schedule_type")
+ def schedule_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["WEEKLY", "MONTHLY"]):
+ raise ValueError("must be one of enum values ('WEEKLY', 'MONTHLY')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of ScheduleInterval from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if hour (nullable) is None
+ # and model_fields_set contains the field
+ if self.hour is None and "hour" in self.model_fields_set:
+ _dict["hour"] = None
+
+ # set to None if minute (nullable) is None
+ # and model_fields_set contains the field
+ if self.minute is None and "minute" in self.model_fields_set:
+ _dict["minute"] = None
+
+ # set to None if days (nullable) is None
+ # and model_fields_set contains the field
+ if self.days is None and "days" in self.model_fields_set:
+ _dict["days"] = None
+
+ # set to None if days_of_month (nullable) is None
+ # and model_fields_set contains the field
+ if self.days_of_month is None and "days_of_month" in self.model_fields_set:
+ _dict["daysOfMonth"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of ScheduleInterval from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "hour": obj.get("hour"),
+ "minute": obj.get("minute"),
+ "days": obj.get("days"),
+ "daysOfMonth": obj.get("daysOfMonth"),
+ "scheduleType": obj.get("scheduleType"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/scored_article.py b/src/perigon/models/scored_data_article.py
similarity index 79%
rename from perigon/models/scored_article.py
rename to src/perigon/models/scored_data_article.py
index 16a5e36..85358b6 100644
--- a/perigon/models/scored_article.py
+++ b/src/perigon/models/scored_data_article.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,22 +20,24 @@
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.article import Article
-class ScoredArticle(BaseModel):
+class ScoredDataArticle(BaseModel):
+ """
+ ScoredDataArticle
"""
- ScoredArticle
- """ # noqa: E501
score: Optional[Union[StrictFloat, StrictInt]] = None
data: Optional[Article] = None
__properties: ClassVar[List[str]] = ["score", "data"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,12 +48,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of ScoredArticle from a JSON string"""
+ """Create an instance of ScoredDataArticle from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -80,11 +80,16 @@ def to_dict(self) -> Dict[str, Any]:
if self.score is None and "score" in self.model_fields_set:
_dict["score"] = None
+ # set to None if data (nullable) is None
+ # and model_fields_set contains the field
+ if self.data is None and "data" in self.model_fields_set:
+ _dict["data"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of ScoredArticle from a dict"""
+ """Create an instance of ScoredDataArticle from a dict"""
if obj is None:
return None
@@ -94,11 +99,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"score": obj.get("score"),
- "data": (
- Article.from_dict(obj["data"])
- if obj.get("data") is not None
- else None
- ),
+ "data": Article.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/scored_data_wiki_data.py b/src/perigon/models/scored_data_wiki_data.py
similarity index 70%
rename from perigon/models/scored_data_wiki_data.py
rename to src/perigon/models/scored_data_wiki_data.py
index 5ab585a..b06c46b 100644
--- a/perigon/models/scored_data_wiki_data.py
+++ b/src/perigon/models/scored_data_wiki_data.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,24 +20,24 @@
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.vector_data import VectorData
from perigon.models.wiki_data import WikiData
class ScoredDataWikiData(BaseModel):
"""
ScoredDataWikiData
- """ # noqa: E501
+ """
score: Optional[Union[StrictFloat, StrictInt]] = None
data: Optional[WikiData] = None
- vectors: Optional[List[VectorData]] = None
- __properties: ClassVar[List[str]] = ["score", "data", "vectors"]
+ __properties: ClassVar[List[str]] = ["score", "data"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -49,8 +48,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,13 +75,6 @@ def to_dict(self) -> Dict[str, Any]:
# override the default output from pydantic by calling `to_dict()` of data
if self.data:
_dict["data"] = self.data.to_dict()
- # override the default output from pydantic by calling `to_dict()` of each item in vectors (list)
- _items = []
- if self.vectors:
- for _item_vectors in self.vectors:
- if _item_vectors:
- _items.append(_item_vectors.to_dict())
- _dict["vectors"] = _items
# set to None if score (nullable) is None
# and model_fields_set contains the field
if self.score is None and "score" in self.model_fields_set:
@@ -94,11 +85,6 @@ def to_dict(self) -> Dict[str, Any]:
if self.data is None and "data" in self.model_fields_set:
_dict["data"] = None
- # set to None if vectors (nullable) is None
- # and model_fields_set contains the field
- if self.vectors is None and "vectors" in self.model_fields_set:
- _dict["vectors"] = None
-
return _dict
@classmethod
@@ -113,16 +99,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"score": obj.get("score"),
- "data": (
- WikiData.from_dict(obj["data"])
- if obj.get("data") is not None
- else None
- ),
- "vectors": (
- [VectorData.from_dict(_item) for _item in obj["vectors"]]
- if obj.get("vectors") is not None
- else None
- ),
+ "data": WikiData.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/sentiment_holder.py b/src/perigon/models/sentiment_holder.py
similarity index 81%
rename from perigon/models/sentiment_holder.py
rename to src/perigon/models/sentiment_holder.py
index 7b0cc47..9d374ff 100644
--- a/perigon/models/sentiment_holder.py
+++ b/src/perigon/models/sentiment_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,22 +19,30 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class SentimentHolder(BaseModel):
"""
- SentimentHolder
- """ # noqa: E501
+ Aggregate sentiment scores for the story.
+ """
- positive: Optional[Union[StrictFloat, StrictInt]] = None
- negative: Optional[Union[StrictFloat, StrictInt]] = None
- neutral: Optional[Union[StrictFloat, StrictInt]] = None
+ positive: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Positive sentiment score from 0 to 1."
+ )
+ negative: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Negative sentiment score from 0 to 1."
+ )
+ neutral: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, description="Neutral sentiment score from 0 to 1."
+ )
__properties: ClassVar[List[str]] = ["positive", "negative", "neutral"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -46,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/signal_api_create_body.py b/src/perigon/models/signal_api_create_body.py
new file mode 100644
index 0000000..d8cda23
--- /dev/null
+++ b/src/perigon/models/signal_api_create_body.py
@@ -0,0 +1,310 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Annotated, Self
+
+from perigon.models.complex_all_endpoint_query import ComplexAllEndpointQuery
+from perigon.models.entity_group import EntityGroup
+from perigon.models.signal_linked_in_query import SignalLinkedInQuery
+from perigon.models.signal_monitoring_plan_data_schema import (
+ SignalMonitoringPlanDataSchema,
+)
+from perigon.models.signal_monitoring_plan_newsletter_config import (
+ SignalMonitoringPlanNewsletterConfig,
+)
+from perigon.models.signal_reddit_query import SignalRedditQuery
+from perigon.models.signal_schedule_policy import SignalSchedulePolicy
+from perigon.models.signal_x_query import SignalXQuery
+
+
+class SignalApiCreateBody(BaseModel):
+ """
+ SignalApiCreateBody
+ """
+
+ name: Annotated[str, Field(min_length=0, strict=True, max_length=256)] = Field(
+ description="Display name for the monitor. Must be non-blank and at most 256 characters."
+ )
+ status: Optional[StrictStr] = Field(
+ default=None,
+ description="Required initial monitor status. Accepts ACTIVE or DRAFT.",
+ )
+ classification_type: StrictStr = Field(
+ description="How matching content is classified. Accepts EVENT, MENTIONS, or TOPIC.",
+ alias="classificationType",
+ )
+ monitoring_objective: Annotated[
+ str, Field(min_length=0, strict=True, max_length=4096)
+ ] = Field(
+ description="Plain-language objective that defines what the monitor tracks. Must be non-blank and at most 4,096 characters.",
+ alias="monitoringObjective",
+ )
+ prompt: Optional[
+ Annotated[str, Field(min_length=0, strict=True, max_length=8192)]
+ ] = Field(
+ default=None,
+ description="Optional instructions used to guide monitor processing. Limited to 8,192 characters.",
+ )
+ data_schema: SignalMonitoringPlanDataSchema = Field(alias="dataSchema")
+ newsletter_config: Optional[SignalMonitoringPlanNewsletterConfig] = Field(
+ default=None, alias="newsletterConfig"
+ )
+ entity_groups: Optional[List[EntityGroup]] = Field(
+ default=None,
+ description="Entity groups the monitor should identify in matching content. Null is treated as an empty list.",
+ alias="entityGroups",
+ )
+ query: ComplexAllEndpointQuery
+ linked_in_query: Optional[SignalLinkedInQuery] = Field(
+ default=None, alias="linkedInQuery"
+ )
+ reddit_query: Optional[SignalRedditQuery] = Field(default=None, alias="redditQuery")
+ schedule_policy: Optional[SignalSchedulePolicy] = Field(
+ default=None, alias="schedulePolicy"
+ )
+ watchlist_id: Optional[StrictInt] = Field(
+ default=None,
+ description="Numeric ID of a visible watchlist in the monitor's organization to associate with the monitor.",
+ alias="watchlistId",
+ )
+ contact_point_ids: Optional[List[UUID]] = Field(
+ default=None,
+ description="UUIDs of verified, non-archived contact points that receive monitor notifications.",
+ alias="contactPointIds",
+ )
+ x_query: Optional[SignalXQuery] = Field(default=None, alias="xQuery")
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "status",
+ "classificationType",
+ "monitoringObjective",
+ "prompt",
+ "dataSchema",
+ "newsletterConfig",
+ "entityGroups",
+ "query",
+ "linkedInQuery",
+ "redditQuery",
+ "schedulePolicy",
+ "watchlistId",
+ "contactPointIds",
+ "xQuery",
+ ]
+
+ @field_validator("status")
+ def status_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["ACTIVE", "DRAFT"]):
+ raise ValueError("must be one of enum values ('ACTIVE', 'DRAFT')")
+ return value
+
+ @field_validator("classification_type")
+ def classification_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["EVENT", "MENTIONS", "TOPIC"]):
+ raise ValueError(
+ "must be one of enum values ('EVENT', 'MENTIONS', 'TOPIC')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalApiCreateBody from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data_schema
+ if self.data_schema:
+ _dict["dataSchema"] = self.data_schema.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of newsletter_config
+ if self.newsletter_config:
+ _dict["newsletterConfig"] = self.newsletter_config.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in entity_groups (list)
+ _items = []
+ if self.entity_groups:
+ for _item_entity_groups in self.entity_groups:
+ _items.append(
+ _item_entity_groups.to_dict()
+ if _item_entity_groups is not None
+ else None
+ )
+ _dict["entityGroups"] = _items
+ # override the default output from pydantic by calling `to_dict()` of query
+ if self.query:
+ _dict["query"] = self.query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of linked_in_query
+ if self.linked_in_query:
+ _dict["linkedInQuery"] = self.linked_in_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of reddit_query
+ if self.reddit_query:
+ _dict["redditQuery"] = self.reddit_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of schedule_policy
+ if self.schedule_policy:
+ _dict["schedulePolicy"] = self.schedule_policy.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of x_query
+ if self.x_query:
+ _dict["xQuery"] = self.x_query.to_dict()
+ # set to None if status (nullable) is None
+ # and model_fields_set contains the field
+ if self.status is None and "status" in self.model_fields_set:
+ _dict["status"] = None
+
+ # set to None if prompt (nullable) is None
+ # and model_fields_set contains the field
+ if self.prompt is None and "prompt" in self.model_fields_set:
+ _dict["prompt"] = None
+
+ # set to None if newsletter_config (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.newsletter_config is None
+ and "newsletter_config" in self.model_fields_set
+ ):
+ _dict["newsletterConfig"] = None
+
+ # set to None if entity_groups (nullable) is None
+ # and model_fields_set contains the field
+ if self.entity_groups is None and "entity_groups" in self.model_fields_set:
+ _dict["entityGroups"] = None
+
+ # set to None if linked_in_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.linked_in_query is None and "linked_in_query" in self.model_fields_set:
+ _dict["linkedInQuery"] = None
+
+ # set to None if reddit_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.reddit_query is None and "reddit_query" in self.model_fields_set:
+ _dict["redditQuery"] = None
+
+ # set to None if schedule_policy (nullable) is None
+ # and model_fields_set contains the field
+ if self.schedule_policy is None and "schedule_policy" in self.model_fields_set:
+ _dict["schedulePolicy"] = None
+
+ # set to None if watchlist_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.watchlist_id is None and "watchlist_id" in self.model_fields_set:
+ _dict["watchlistId"] = None
+
+ # set to None if contact_point_ids (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.contact_point_ids is None
+ and "contact_point_ids" in self.model_fields_set
+ ):
+ _dict["contactPointIds"] = None
+
+ # set to None if x_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_query is None and "x_query" in self.model_fields_set:
+ _dict["xQuery"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalApiCreateBody from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "status": obj.get("status"),
+ "classificationType": obj.get("classificationType"),
+ "monitoringObjective": obj.get("monitoringObjective"),
+ "prompt": obj.get("prompt"),
+ "dataSchema": SignalMonitoringPlanDataSchema.from_dict(
+ obj["dataSchema"]
+ )
+ if obj.get("dataSchema") is not None
+ else None,
+ "newsletterConfig": SignalMonitoringPlanNewsletterConfig.from_dict(
+ obj["newsletterConfig"]
+ )
+ if obj.get("newsletterConfig") is not None
+ else None,
+ "entityGroups": [
+ EntityGroup.from_dict(_item) for _item in obj["entityGroups"]
+ ]
+ if obj.get("entityGroups") is not None
+ else None,
+ "query": ComplexAllEndpointQuery.from_dict(obj["query"])
+ if obj.get("query") is not None
+ else None,
+ "linkedInQuery": SignalLinkedInQuery.from_dict(obj["linkedInQuery"])
+ if obj.get("linkedInQuery") is not None
+ else None,
+ "redditQuery": SignalRedditQuery.from_dict(obj["redditQuery"])
+ if obj.get("redditQuery") is not None
+ else None,
+ "schedulePolicy": SignalSchedulePolicy.from_dict(obj["schedulePolicy"])
+ if obj.get("schedulePolicy") is not None
+ else None,
+ "watchlistId": obj.get("watchlistId"),
+ "contactPointIds": obj.get("contactPointIds"),
+ "xQuery": SignalXQuery.from_dict(obj["xQuery"])
+ if obj.get("xQuery") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_api_dto.py b/src/perigon/models/signal_api_dto.py
new file mode 100644
index 0000000..a9c421c
--- /dev/null
+++ b/src/perigon/models/signal_api_dto.py
@@ -0,0 +1,381 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.complex_all_endpoint_query import ComplexAllEndpointQuery
+from perigon.models.entity_group import EntityGroup
+from perigon.models.signal_linked_in_query import SignalLinkedInQuery
+from perigon.models.signal_monitoring_plan_data_schema import (
+ SignalMonitoringPlanDataSchema,
+)
+from perigon.models.signal_monitoring_plan_newsletter_config import (
+ SignalMonitoringPlanNewsletterConfig,
+)
+from perigon.models.signal_reddit_query import SignalRedditQuery
+from perigon.models.signal_schedule_policy import SignalSchedulePolicy
+from perigon.models.signal_x_query import SignalXQuery
+
+
+class SignalApiDto(BaseModel):
+ """
+ Monitor payload.
+ """
+
+ uuid: Optional[UUID] = Field(
+ default=None, description="Unique identifier for the monitor."
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Display name of the monitor."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the monitor was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the monitor was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ status: Optional[StrictStr] = Field(
+ default=None,
+ description="Current lifecycle status: DRAFT, ACTIVE, STOPPED, or ARCHIVED.",
+ )
+ classification_type: Optional[StrictStr] = Field(
+ default=None,
+ description="How matching content is classified: EVENT, MENTIONS, or TOPIC.",
+ alias="classificationType",
+ )
+ monitoring_objective: Optional[StrictStr] = Field(
+ default=None,
+ description="Plain-language objective that defines what the monitor tracks.",
+ alias="monitoringObjective",
+ )
+ prompt: Optional[StrictStr] = Field(
+ default=None,
+ description="Optional instructions used to guide monitor processing.",
+ )
+ data_schema: Optional[SignalMonitoringPlanDataSchema] = Field(
+ default=None, alias="dataSchema"
+ )
+ newsletter_config: Optional[SignalMonitoringPlanNewsletterConfig] = Field(
+ default=None, alias="newsletterConfig"
+ )
+ entity_groups: Optional[List[EntityGroup]] = Field(
+ default=None,
+ description="Entity groups the monitor identifies in matching content.",
+ alias="entityGroups",
+ )
+ query: Optional[ComplexAllEndpointQuery] = None
+ linked_in_query: Optional[SignalLinkedInQuery] = Field(
+ default=None, alias="linkedInQuery"
+ )
+ reddit_query: Optional[SignalRedditQuery] = Field(default=None, alias="redditQuery")
+ schedule_policy: Optional[SignalSchedulePolicy] = Field(
+ default=None, alias="schedulePolicy"
+ )
+ watchlist_id: Optional[StrictInt] = Field(
+ default=None,
+ description="Numeric ID of the watchlist associated with the monitor, if any.",
+ alias="watchlistId",
+ )
+ contact_point_ids: Optional[List[UUID]] = Field(
+ default=None,
+ description="UUIDs of contact points configured to receive monitor notifications.",
+ alias="contactPointIds",
+ )
+ x_query: Optional[SignalXQuery] = Field(default=None, alias="xQuery")
+ __properties: ClassVar[List[str]] = [
+ "uuid",
+ "name",
+ "createdAt",
+ "updatedAt",
+ "status",
+ "classificationType",
+ "monitoringObjective",
+ "prompt",
+ "dataSchema",
+ "newsletterConfig",
+ "entityGroups",
+ "query",
+ "linkedInQuery",
+ "redditQuery",
+ "schedulePolicy",
+ "watchlistId",
+ "contactPointIds",
+ "xQuery",
+ ]
+
+ @field_validator("status")
+ def status_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["DRAFT", "ACTIVE", "STOPPED", "ARCHIVED"]):
+ raise ValueError(
+ "must be one of enum values ('DRAFT', 'ACTIVE', 'STOPPED', 'ARCHIVED')"
+ )
+ return value
+
+ @field_validator("classification_type")
+ def classification_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["EVENT", "MENTIONS", "TOPIC"]):
+ raise ValueError(
+ "must be one of enum values ('EVENT', 'MENTIONS', 'TOPIC')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalApiDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data_schema
+ if self.data_schema:
+ _dict["dataSchema"] = self.data_schema.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of newsletter_config
+ if self.newsletter_config:
+ _dict["newsletterConfig"] = self.newsletter_config.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in entity_groups (list)
+ _items = []
+ if self.entity_groups:
+ for _item_entity_groups in self.entity_groups:
+ _items.append(
+ _item_entity_groups.to_dict()
+ if _item_entity_groups is not None
+ else None
+ )
+ _dict["entityGroups"] = _items
+ # override the default output from pydantic by calling `to_dict()` of query
+ if self.query:
+ _dict["query"] = self.query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of linked_in_query
+ if self.linked_in_query:
+ _dict["linkedInQuery"] = self.linked_in_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of reddit_query
+ if self.reddit_query:
+ _dict["redditQuery"] = self.reddit_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of schedule_policy
+ if self.schedule_policy:
+ _dict["schedulePolicy"] = self.schedule_policy.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of x_query
+ if self.x_query:
+ _dict["xQuery"] = self.x_query.to_dict()
+ # set to None if uuid (nullable) is None
+ # and model_fields_set contains the field
+ if self.uuid is None and "uuid" in self.model_fields_set:
+ _dict["uuid"] = None
+
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if status (nullable) is None
+ # and model_fields_set contains the field
+ if self.status is None and "status" in self.model_fields_set:
+ _dict["status"] = None
+
+ # set to None if classification_type (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.classification_type is None
+ and "classification_type" in self.model_fields_set
+ ):
+ _dict["classificationType"] = None
+
+ # set to None if monitoring_objective (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.monitoring_objective is None
+ and "monitoring_objective" in self.model_fields_set
+ ):
+ _dict["monitoringObjective"] = None
+
+ # set to None if prompt (nullable) is None
+ # and model_fields_set contains the field
+ if self.prompt is None and "prompt" in self.model_fields_set:
+ _dict["prompt"] = None
+
+ # set to None if data_schema (nullable) is None
+ # and model_fields_set contains the field
+ if self.data_schema is None and "data_schema" in self.model_fields_set:
+ _dict["dataSchema"] = None
+
+ # set to None if newsletter_config (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.newsletter_config is None
+ and "newsletter_config" in self.model_fields_set
+ ):
+ _dict["newsletterConfig"] = None
+
+ # set to None if entity_groups (nullable) is None
+ # and model_fields_set contains the field
+ if self.entity_groups is None and "entity_groups" in self.model_fields_set:
+ _dict["entityGroups"] = None
+
+ # set to None if query (nullable) is None
+ # and model_fields_set contains the field
+ if self.query is None and "query" in self.model_fields_set:
+ _dict["query"] = None
+
+ # set to None if linked_in_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.linked_in_query is None and "linked_in_query" in self.model_fields_set:
+ _dict["linkedInQuery"] = None
+
+ # set to None if reddit_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.reddit_query is None and "reddit_query" in self.model_fields_set:
+ _dict["redditQuery"] = None
+
+ # set to None if schedule_policy (nullable) is None
+ # and model_fields_set contains the field
+ if self.schedule_policy is None and "schedule_policy" in self.model_fields_set:
+ _dict["schedulePolicy"] = None
+
+ # set to None if watchlist_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.watchlist_id is None and "watchlist_id" in self.model_fields_set:
+ _dict["watchlistId"] = None
+
+ # set to None if contact_point_ids (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.contact_point_ids is None
+ and "contact_point_ids" in self.model_fields_set
+ ):
+ _dict["contactPointIds"] = None
+
+ # set to None if x_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_query is None and "x_query" in self.model_fields_set:
+ _dict["xQuery"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalApiDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "uuid": obj.get("uuid"),
+ "name": obj.get("name"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "status": obj.get("status"),
+ "classificationType": obj.get("classificationType"),
+ "monitoringObjective": obj.get("monitoringObjective"),
+ "prompt": obj.get("prompt"),
+ "dataSchema": SignalMonitoringPlanDataSchema.from_dict(
+ obj["dataSchema"]
+ )
+ if obj.get("dataSchema") is not None
+ else None,
+ "newsletterConfig": SignalMonitoringPlanNewsletterConfig.from_dict(
+ obj["newsletterConfig"]
+ )
+ if obj.get("newsletterConfig") is not None
+ else None,
+ "entityGroups": [
+ EntityGroup.from_dict(_item) for _item in obj["entityGroups"]
+ ]
+ if obj.get("entityGroups") is not None
+ else None,
+ "query": ComplexAllEndpointQuery.from_dict(obj["query"])
+ if obj.get("query") is not None
+ else None,
+ "linkedInQuery": SignalLinkedInQuery.from_dict(obj["linkedInQuery"])
+ if obj.get("linkedInQuery") is not None
+ else None,
+ "redditQuery": SignalRedditQuery.from_dict(obj["redditQuery"])
+ if obj.get("redditQuery") is not None
+ else None,
+ "schedulePolicy": SignalSchedulePolicy.from_dict(obj["schedulePolicy"])
+ if obj.get("schedulePolicy") is not None
+ else None,
+ "watchlistId": obj.get("watchlistId"),
+ "contactPointIds": obj.get("contactPointIds"),
+ "xQuery": SignalXQuery.from_dict(obj["xQuery"])
+ if obj.get("xQuery") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_api_update_body.py b/src/perigon/models/signal_api_update_body.py
new file mode 100644
index 0000000..a4c9ab5
--- /dev/null
+++ b/src/perigon/models/signal_api_update_body.py
@@ -0,0 +1,262 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Annotated, Self
+
+from perigon.models.complex_all_endpoint_query import ComplexAllEndpointQuery
+from perigon.models.entity_group import EntityGroup
+from perigon.models.signal_linked_in_query import SignalLinkedInQuery
+from perigon.models.signal_monitoring_plan_data_schema import (
+ SignalMonitoringPlanDataSchema,
+)
+from perigon.models.signal_monitoring_plan_newsletter_config import (
+ SignalMonitoringPlanNewsletterConfig,
+)
+from perigon.models.signal_reddit_query import SignalRedditQuery
+from perigon.models.signal_schedule_policy import SignalSchedulePolicy
+from perigon.models.signal_x_query import SignalXQuery
+
+
+class SignalApiUpdateBody(BaseModel):
+ """
+ SignalApiUpdateBody
+ """
+
+ name: Annotated[str, Field(min_length=0, strict=True, max_length=256)] = Field(
+ description="New display name for the monitor. Must be non-blank and at most 256 characters."
+ )
+ classification_type: StrictStr = Field(
+ description="New classification behavior. Accepts EVENT, MENTIONS, or TOPIC.",
+ alias="classificationType",
+ )
+ monitoring_objective: Annotated[
+ str, Field(min_length=0, strict=True, max_length=4096)
+ ] = Field(
+ description="New plain-language monitoring objective. Must be non-blank and at most 4,096 characters.",
+ alias="monitoringObjective",
+ )
+ prompt: Optional[
+ Annotated[str, Field(min_length=0, strict=True, max_length=8192)]
+ ] = Field(
+ default=None,
+ description="New instructions used to guide monitor processing. Limited to 8,192 characters and may be cleared with null.",
+ )
+ data_schema: SignalMonitoringPlanDataSchema = Field(alias="dataSchema")
+ newsletter_config: Optional[SignalMonitoringPlanNewsletterConfig] = Field(
+ default=None, alias="newsletterConfig"
+ )
+ entity_groups: List[EntityGroup] = Field(
+ description="Replacement entity groups for the monitor. Send an empty list to remove all groups.",
+ alias="entityGroups",
+ )
+ query: ComplexAllEndpointQuery
+ linked_in_query: SignalLinkedInQuery = Field(alias="linkedInQuery")
+ reddit_query: SignalRedditQuery = Field(alias="redditQuery")
+ schedule_policy: Optional[SignalSchedulePolicy] = Field(
+ default=None, alias="schedulePolicy"
+ )
+ watchlist_id: Optional[StrictInt] = Field(
+ default=None,
+ description="Numeric ID of a visible watchlist in the monitor's organization to associate with the monitor.",
+ alias="watchlistId",
+ )
+ contact_point_ids: List[UUID] = Field(
+ description="Replacement UUIDs of verified, non-archived contact points. Send an empty list to remove all contact points.",
+ alias="contactPointIds",
+ )
+ x_query: Optional[SignalXQuery] = Field(default=None, alias="xQuery")
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "classificationType",
+ "monitoringObjective",
+ "prompt",
+ "dataSchema",
+ "newsletterConfig",
+ "entityGroups",
+ "query",
+ "linkedInQuery",
+ "redditQuery",
+ "schedulePolicy",
+ "watchlistId",
+ "contactPointIds",
+ "xQuery",
+ ]
+
+ @field_validator("classification_type")
+ def classification_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["EVENT", "MENTIONS", "TOPIC"]):
+ raise ValueError(
+ "must be one of enum values ('EVENT', 'MENTIONS', 'TOPIC')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalApiUpdateBody from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data_schema
+ if self.data_schema:
+ _dict["dataSchema"] = self.data_schema.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of newsletter_config
+ if self.newsletter_config:
+ _dict["newsletterConfig"] = self.newsletter_config.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in entity_groups (list)
+ _items = []
+ if self.entity_groups:
+ for _item_entity_groups in self.entity_groups:
+ _items.append(
+ _item_entity_groups.to_dict()
+ if _item_entity_groups is not None
+ else None
+ )
+ _dict["entityGroups"] = _items
+ # override the default output from pydantic by calling `to_dict()` of query
+ if self.query:
+ _dict["query"] = self.query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of linked_in_query
+ if self.linked_in_query:
+ _dict["linkedInQuery"] = self.linked_in_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of reddit_query
+ if self.reddit_query:
+ _dict["redditQuery"] = self.reddit_query.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of schedule_policy
+ if self.schedule_policy:
+ _dict["schedulePolicy"] = self.schedule_policy.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of x_query
+ if self.x_query:
+ _dict["xQuery"] = self.x_query.to_dict()
+ # set to None if prompt (nullable) is None
+ # and model_fields_set contains the field
+ if self.prompt is None and "prompt" in self.model_fields_set:
+ _dict["prompt"] = None
+
+ # set to None if newsletter_config (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.newsletter_config is None
+ and "newsletter_config" in self.model_fields_set
+ ):
+ _dict["newsletterConfig"] = None
+
+ # set to None if schedule_policy (nullable) is None
+ # and model_fields_set contains the field
+ if self.schedule_policy is None and "schedule_policy" in self.model_fields_set:
+ _dict["schedulePolicy"] = None
+
+ # set to None if watchlist_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.watchlist_id is None and "watchlist_id" in self.model_fields_set:
+ _dict["watchlistId"] = None
+
+ # set to None if x_query (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_query is None and "x_query" in self.model_fields_set:
+ _dict["xQuery"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalApiUpdateBody from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "classificationType": obj.get("classificationType"),
+ "monitoringObjective": obj.get("monitoringObjective"),
+ "prompt": obj.get("prompt"),
+ "dataSchema": SignalMonitoringPlanDataSchema.from_dict(
+ obj["dataSchema"]
+ )
+ if obj.get("dataSchema") is not None
+ else None,
+ "newsletterConfig": SignalMonitoringPlanNewsletterConfig.from_dict(
+ obj["newsletterConfig"]
+ )
+ if obj.get("newsletterConfig") is not None
+ else None,
+ "entityGroups": [
+ EntityGroup.from_dict(_item) for _item in obj["entityGroups"]
+ ]
+ if obj.get("entityGroups") is not None
+ else None,
+ "query": ComplexAllEndpointQuery.from_dict(obj["query"])
+ if obj.get("query") is not None
+ else None,
+ "linkedInQuery": SignalLinkedInQuery.from_dict(obj["linkedInQuery"])
+ if obj.get("linkedInQuery") is not None
+ else None,
+ "redditQuery": SignalRedditQuery.from_dict(obj["redditQuery"])
+ if obj.get("redditQuery") is not None
+ else None,
+ "schedulePolicy": SignalSchedulePolicy.from_dict(obj["schedulePolicy"])
+ if obj.get("schedulePolicy") is not None
+ else None,
+ "watchlistId": obj.get("watchlistId"),
+ "contactPointIds": obj.get("contactPointIds"),
+ "xQuery": SignalXQuery.from_dict(obj["xQuery"])
+ if obj.get("xQuery") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_article_partial_dto.py b/src/perigon/models/signal_article_partial_dto.py
new file mode 100644
index 0000000..3ab705b
--- /dev/null
+++ b/src/perigon/models/signal_article_partial_dto.py
@@ -0,0 +1,149 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalArticlePartialDto(BaseModel):
+ """
+ Article details keyed by article ID for articles related to the event.
+ """
+
+ title: Optional[StrictStr] = Field(default=None, description="Article headline.")
+ url: Optional[StrictStr] = Field(
+ default=None, description="Canonical URL of the article."
+ )
+ domain: Optional[StrictStr] = Field(
+ default=None, description="Publisher domain for the article."
+ )
+ pub_date: Optional[StrictStr] = Field(
+ default=None,
+ description="Article publication date supplied with the monitor result.",
+ alias="pubDate",
+ )
+ source_favicon: Optional[StrictStr] = Field(
+ default=None,
+ description="URL of the publisher's favicon.",
+ alias="sourceFavicon",
+ )
+ highlights: Optional[Dict[str, List[StrictStr]]] = Field(
+ default=None, description="Matched text snippets grouped by article field."
+ )
+ __properties: ClassVar[List[str]] = [
+ "title",
+ "url",
+ "domain",
+ "pubDate",
+ "sourceFavicon",
+ "highlights",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalArticlePartialDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if domain (nullable) is None
+ # and model_fields_set contains the field
+ if self.domain is None and "domain" in self.model_fields_set:
+ _dict["domain"] = None
+
+ # set to None if pub_date (nullable) is None
+ # and model_fields_set contains the field
+ if self.pub_date is None and "pub_date" in self.model_fields_set:
+ _dict["pubDate"] = None
+
+ # set to None if source_favicon (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_favicon is None and "source_favicon" in self.model_fields_set:
+ _dict["sourceFavicon"] = None
+
+ # set to None if highlights (nullable) is None
+ # and model_fields_set contains the field
+ if self.highlights is None and "highlights" in self.model_fields_set:
+ _dict["highlights"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalArticlePartialDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "title": obj.get("title"),
+ "url": obj.get("url"),
+ "domain": obj.get("domain"),
+ "pubDate": obj.get("pubDate"),
+ "sourceFavicon": obj.get("sourceFavicon"),
+ "highlights": obj.get("highlights"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_event_dto.py b/src/perigon/models/signal_event_dto.py
new file mode 100644
index 0000000..d4d4f52
--- /dev/null
+++ b/src/perigon/models/signal_event_dto.py
@@ -0,0 +1,342 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.fillbot_metadata import FillbotMetadata
+from perigon.models.matched_entity_data import MatchedEntityData
+from perigon.models.signal_article_partial_dto import SignalArticlePartialDto
+from perigon.models.x_post import XPost
+
+
+class SignalEventDto(BaseModel):
+ """
+ Events returned for the requested page.
+ """
+
+ uuid: Optional[UUID] = Field(
+ default=None, description="Unique identifier for the monitor event."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the event was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the event was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ event_date: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the detected event occurred, in ISO 8601 format when available.",
+ alias="eventDate",
+ )
+ event_type: Optional[StrictStr] = Field(
+ default=None,
+ description="Detected event category produced by the monitor.",
+ alias="eventType",
+ )
+ data: Optional[Dict[str, Dict[str, Any]]] = Field(
+ default=None,
+ description="Structured event fields defined by the monitor's data schema. May also include standard source fields describing the event's primary source: url, pubDate, authorsByline, language, title, content (Mentions social posts only), and sourceChannel (NEWS, LINKEDIN, REDDIT, or X). These fields are not guaranteed; older events or events whose producer did not supply them may omit any of them.",
+ )
+ related_article_ids: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Perigon article IDs related to the event.",
+ alias="relatedArticleIds",
+ )
+ articles: Optional[Dict[str, SignalArticlePartialDto]] = Field(
+ default=None,
+ description="Article details keyed by article ID for articles related to the event.",
+ )
+ entities: Optional[List[MatchedEntityData]] = Field(
+ default=None,
+ description="Entities matched in the content that produced the event.",
+ )
+ summary: Optional[StrictStr] = Field(
+ default=None, description="AI-generated summary of the event, when available."
+ )
+ fillbot_metadata: Optional[FillbotMetadata] = Field(
+ default=None, alias="fillbotMetadata"
+ )
+ fillbot_schedule_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time enrichment is scheduled to run, in ISO 8601 format.",
+ alias="fillbotScheduleAt",
+ )
+ fillbot_last_run_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time enrichment last ran, in ISO 8601 format.",
+ alias="fillbotLastRunAt",
+ )
+ fillbot_status: Optional[StrictStr] = Field(
+ default=None,
+ description="Current event enrichment status. Accepts PENDING, IN_PROGRESS, COMPLETE, or EXHAUSTED.",
+ alias="fillbotStatus",
+ )
+ duplicate: Optional[StrictBool] = None
+ x_posts: Optional[List[XPost]] = Field(default=None, alias="xPosts")
+ __properties: ClassVar[List[str]] = [
+ "uuid",
+ "createdAt",
+ "updatedAt",
+ "eventDate",
+ "eventType",
+ "data",
+ "relatedArticleIds",
+ "articles",
+ "entities",
+ "summary",
+ "fillbotMetadata",
+ "fillbotScheduleAt",
+ "fillbotLastRunAt",
+ "fillbotStatus",
+ "duplicate",
+ "xPosts",
+ ]
+
+ @field_validator("fillbot_status")
+ def fillbot_status_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["PENDING", "IN_PROGRESS", "COMPLETE", "EXHAUSTED"]):
+ raise ValueError(
+ "must be one of enum values ('PENDING', 'IN_PROGRESS', 'COMPLETE', 'EXHAUSTED')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalEventDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each value in articles (dict)
+ _field_dict = {}
+ if self.articles:
+ for _key_articles in self.articles:
+ _field_dict[_key_articles] = (
+ self.articles[_key_articles].to_dict()
+ if self.articles[_key_articles] is not None
+ else None
+ )
+ _dict["articles"] = _field_dict
+ # override the default output from pydantic by calling `to_dict()` of each item in entities (list)
+ _items = []
+ if self.entities:
+ for _item_entities in self.entities:
+ _items.append(
+ _item_entities.to_dict() if _item_entities is not None else None
+ )
+ _dict["entities"] = _items
+ # override the default output from pydantic by calling `to_dict()` of fillbot_metadata
+ if self.fillbot_metadata:
+ _dict["fillbotMetadata"] = self.fillbot_metadata.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of each item in x_posts (list)
+ _items = []
+ if self.x_posts:
+ for _item_x_posts in self.x_posts:
+ _items.append(
+ _item_x_posts.to_dict() if _item_x_posts is not None else None
+ )
+ _dict["xPosts"] = _items
+ # set to None if uuid (nullable) is None
+ # and model_fields_set contains the field
+ if self.uuid is None and "uuid" in self.model_fields_set:
+ _dict["uuid"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if event_date (nullable) is None
+ # and model_fields_set contains the field
+ if self.event_date is None and "event_date" in self.model_fields_set:
+ _dict["eventDate"] = None
+
+ # set to None if event_type (nullable) is None
+ # and model_fields_set contains the field
+ if self.event_type is None and "event_type" in self.model_fields_set:
+ _dict["eventType"] = None
+
+ # set to None if data (nullable) is None
+ # and model_fields_set contains the field
+ if self.data is None and "data" in self.model_fields_set:
+ _dict["data"] = None
+
+ # set to None if related_article_ids (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.related_article_ids is None
+ and "related_article_ids" in self.model_fields_set
+ ):
+ _dict["relatedArticleIds"] = None
+
+ # set to None if articles (nullable) is None
+ # and model_fields_set contains the field
+ if self.articles is None and "articles" in self.model_fields_set:
+ _dict["articles"] = None
+
+ # set to None if entities (nullable) is None
+ # and model_fields_set contains the field
+ if self.entities is None and "entities" in self.model_fields_set:
+ _dict["entities"] = None
+
+ # set to None if summary (nullable) is None
+ # and model_fields_set contains the field
+ if self.summary is None and "summary" in self.model_fields_set:
+ _dict["summary"] = None
+
+ # set to None if fillbot_metadata (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.fillbot_metadata is None
+ and "fillbot_metadata" in self.model_fields_set
+ ):
+ _dict["fillbotMetadata"] = None
+
+ # set to None if fillbot_schedule_at (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.fillbot_schedule_at is None
+ and "fillbot_schedule_at" in self.model_fields_set
+ ):
+ _dict["fillbotScheduleAt"] = None
+
+ # set to None if fillbot_last_run_at (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.fillbot_last_run_at is None
+ and "fillbot_last_run_at" in self.model_fields_set
+ ):
+ _dict["fillbotLastRunAt"] = None
+
+ # set to None if fillbot_status (nullable) is None
+ # and model_fields_set contains the field
+ if self.fillbot_status is None and "fillbot_status" in self.model_fields_set:
+ _dict["fillbotStatus"] = None
+
+ # set to None if duplicate (nullable) is None
+ # and model_fields_set contains the field
+ if self.duplicate is None and "duplicate" in self.model_fields_set:
+ _dict["duplicate"] = None
+
+ # set to None if x_posts (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_posts is None and "x_posts" in self.model_fields_set:
+ _dict["xPosts"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalEventDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "uuid": obj.get("uuid"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "eventDate": obj.get("eventDate"),
+ "eventType": obj.get("eventType"),
+ "data": obj.get("data"),
+ "relatedArticleIds": obj.get("relatedArticleIds"),
+ "articles": dict(
+ (_k, SignalArticlePartialDto.from_dict(_v))
+ for _k, _v in obj["articles"].items()
+ )
+ if obj.get("articles") is not None
+ else None,
+ "entities": [
+ MatchedEntityData.from_dict(_item) for _item in obj["entities"]
+ ]
+ if obj.get("entities") is not None
+ else None,
+ "summary": obj.get("summary"),
+ "fillbotMetadata": FillbotMetadata.from_dict(obj["fillbotMetadata"])
+ if obj.get("fillbotMetadata") is not None
+ else None,
+ "fillbotScheduleAt": obj.get("fillbotScheduleAt"),
+ "fillbotLastRunAt": obj.get("fillbotLastRunAt"),
+ "fillbotStatus": obj.get("fillbotStatus"),
+ "duplicate": obj.get("duplicate"),
+ "xPosts": [XPost.from_dict(_item) for _item in obj["xPosts"]]
+ if obj.get("xPosts") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_linked_in_query.py b/src/perigon/models/signal_linked_in_query.py
new file mode 100644
index 0000000..1577ee1
--- /dev/null
+++ b/src/perigon/models/signal_linked_in_query.py
@@ -0,0 +1,141 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalLinkedInQuery(BaseModel):
+ """
+ SignalLinkedInQuery
+ """
+
+ version: StrictStr
+ keyword: StrictStr
+ content_type: Optional[StrictStr] = Field(default=None, alias="contentType")
+ author_title: Optional[StrictStr] = Field(default=None, alias="authorTitle")
+ mentions_organization: Optional[List[StrictInt]] = Field(
+ default=None, alias="mentionsOrganization"
+ )
+ __properties: ClassVar[List[str]] = [
+ "version",
+ "keyword",
+ "contentType",
+ "authorTitle",
+ "mentionsOrganization",
+ ]
+
+ @field_validator("version")
+ def version_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["V1"]):
+ raise ValueError("must be one of enum values ('V1')")
+ return value
+
+ @field_validator("content_type")
+ def content_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value is None:
+ return value
+
+ if value not in set(["PHOTOS", "VIDEOS", "JOBS", "LIVE_VIDEOS", "DOCUMENTS"]):
+ raise ValueError(
+ "must be one of enum values ('PHOTOS', 'VIDEOS', 'JOBS', 'LIVE_VIDEOS', 'DOCUMENTS')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalLinkedInQuery from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if content_type (nullable) is None
+ # and model_fields_set contains the field
+ if self.content_type is None and "content_type" in self.model_fields_set:
+ _dict["contentType"] = None
+
+ # set to None if author_title (nullable) is None
+ # and model_fields_set contains the field
+ if self.author_title is None and "author_title" in self.model_fields_set:
+ _dict["authorTitle"] = None
+
+ # set to None if mentions_organization (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.mentions_organization is None
+ and "mentions_organization" in self.model_fields_set
+ ):
+ _dict["mentionsOrganization"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalLinkedInQuery from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "version": obj.get("version"),
+ "keyword": obj.get("keyword"),
+ "contentType": obj.get("contentType"),
+ "authorTitle": obj.get("authorTitle"),
+ "mentionsOrganization": obj.get("mentionsOrganization"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_monitoring_plan_data_schema.py b/src/perigon/models/signal_monitoring_plan_data_schema.py
new file mode 100644
index 0000000..4a2d82a
--- /dev/null
+++ b/src/perigon/models/signal_monitoring_plan_data_schema.py
@@ -0,0 +1,139 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_monitoring_plan_data_schema_property import (
+ SignalMonitoringPlanDataSchemaProperty,
+)
+
+
+class SignalMonitoringPlanDataSchema(BaseModel):
+ """
+ Schema for the structured data extracted into each matching event.
+ """
+
+ version: StrictStr = Field(
+ description="Data schema version. Currently only V1 is supported."
+ )
+ allow_extra: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether extracted event data may contain fields not defined in schema. Defaults to false.",
+ alias="allowExtra",
+ )
+ var_schema: Dict[str, SignalMonitoringPlanDataSchemaProperty] = Field(
+ description="Structured event field definitions keyed by output field name.",
+ alias="schema",
+ )
+ __properties: ClassVar[List[str]] = ["version", "allowExtra", "schema"]
+
+ @field_validator("version")
+ def version_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["V1"]):
+ raise ValueError("must be one of enum values ('V1')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanDataSchema from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each value in var_schema (dict)
+ _field_dict = {}
+ if self.var_schema:
+ for _key_var_schema in self.var_schema:
+ _field_dict[_key_var_schema] = (
+ self.var_schema[_key_var_schema].to_dict()
+ if self.var_schema[_key_var_schema] is not None
+ else None
+ )
+ _dict["schema"] = _field_dict
+ # set to None if allow_extra (nullable) is None
+ # and model_fields_set contains the field
+ if self.allow_extra is None and "allow_extra" in self.model_fields_set:
+ _dict["allowExtra"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanDataSchema from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "version": obj.get("version"),
+ "allowExtra": obj.get("allowExtra"),
+ "schema": dict(
+ (_k, SignalMonitoringPlanDataSchemaProperty.from_dict(_v))
+ for _k, _v in obj["schema"].items()
+ )
+ if obj.get("schema") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_monitoring_plan_data_schema_property.py b/src/perigon/models/signal_monitoring_plan_data_schema_property.py
new file mode 100644
index 0000000..7d26132
--- /dev/null
+++ b/src/perigon/models/signal_monitoring_plan_data_schema_property.py
@@ -0,0 +1,149 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalMonitoringPlanDataSchemaProperty(BaseModel):
+ """
+ Structured event field definitions keyed by output field name.
+ """
+
+ type: StrictStr = Field(
+ description="Value type extracted for this field. Accepts ARRAY, BOOLEAN, DATE, DATETIME, ENUM, INTEGER, NUMBER, OBJECT, STRING, or URL."
+ )
+ description: Optional[StrictStr] = Field(
+ default=None,
+ description="Plain-language instructions describing the value to extract for this field.",
+ )
+ nullable: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the extracted value may be null. Defaults to true.",
+ )
+ required: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether the field must appear in extracted event data. Defaults to true.",
+ )
+ __properties: ClassVar[List[str]] = ["type", "description", "nullable", "required"]
+
+ @field_validator("type")
+ def type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(
+ [
+ "ARRAY",
+ "BOOLEAN",
+ "DATE",
+ "DATETIME",
+ "ENUM",
+ "INTEGER",
+ "NUMBER",
+ "OBJECT",
+ "STRING",
+ "URL",
+ ]
+ ):
+ raise ValueError(
+ "must be one of enum values ('ARRAY', 'BOOLEAN', 'DATE', 'DATETIME', 'ENUM', 'INTEGER', 'NUMBER', 'OBJECT', 'STRING', 'URL')"
+ )
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanDataSchemaProperty from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if nullable (nullable) is None
+ # and model_fields_set contains the field
+ if self.nullable is None and "nullable" in self.model_fields_set:
+ _dict["nullable"] = None
+
+ # set to None if required (nullable) is None
+ # and model_fields_set contains the field
+ if self.required is None and "required" in self.model_fields_set:
+ _dict["required"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanDataSchemaProperty from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "type": obj.get("type"),
+ "description": obj.get("description"),
+ "nullable": obj.get("nullable"),
+ "required": obj.get("required"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_monitoring_plan_newsletter_config.py b/src/perigon/models/signal_monitoring_plan_newsletter_config.py
new file mode 100644
index 0000000..dceb258
--- /dev/null
+++ b/src/perigon/models/signal_monitoring_plan_newsletter_config.py
@@ -0,0 +1,115 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictStr,
+ field_validator,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalMonitoringPlanNewsletterConfig(BaseModel):
+ """
+ Citation settings for newsletters generated by the monitor.
+ """
+
+ citations: Optional[StrictBool] = Field(
+ default=None,
+ description="Whether generated newsletters include citations. Defaults to true.",
+ )
+ story_citation_resolving_type: StrictStr = Field(
+ description="How story citations are resolved. Accepts STORY or TOP_ARTICLE.",
+ alias="storyCitationResolvingType",
+ )
+ __properties: ClassVar[List[str]] = ["citations", "storyCitationResolvingType"]
+
+ @field_validator("story_citation_resolving_type")
+ def story_citation_resolving_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["STORY", "TOP_ARTICLE"]):
+ raise ValueError("must be one of enum values ('STORY', 'TOP_ARTICLE')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanNewsletterConfig from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if citations (nullable) is None
+ # and model_fields_set contains the field
+ if self.citations is None and "citations" in self.model_fields_set:
+ _dict["citations"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalMonitoringPlanNewsletterConfig from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "citations": obj.get("citations"),
+ "storyCitationResolvingType": obj.get("storyCitationResolvingType"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_newsletter_dto.py b/src/perigon/models/signal_newsletter_dto.py
new file mode 100644
index 0000000..3a36e2d
--- /dev/null
+++ b/src/perigon/models/signal_newsletter_dto.py
@@ -0,0 +1,223 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+from uuid import UUID
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.signal_article_partial_dto import SignalArticlePartialDto
+from perigon.models.signal_newsletter_story_partial_dto import (
+ SignalNewsletterStoryPartialDto,
+)
+from perigon.models.x_post import XPost
+
+
+class SignalNewsletterDto(BaseModel):
+ """
+ Newsletters returned for the requested page.
+ """
+
+ uuid: Optional[UUID] = Field(
+ default=None, description="Unique identifier for the newsletter."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the newsletter was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the newsletter was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ title: Optional[StrictStr] = Field(
+ default=None, description="Generated title of the newsletter."
+ )
+ content: Optional[StrictStr] = Field(
+ default=None, description="Generated newsletter content."
+ )
+ articles: Optional[Dict[str, SignalArticlePartialDto]] = Field(
+ default=None,
+ description="Article details keyed by article ID for content cited in the newsletter.",
+ )
+ stories: Optional[Dict[str, SignalNewsletterStoryPartialDto]] = Field(
+ default=None,
+ description="Story details keyed by story identifier for stories cited in the newsletter.",
+ )
+ x_posts: Optional[Dict[str, XPost]] = Field(default=None, alias="xPosts")
+ __properties: ClassVar[List[str]] = [
+ "uuid",
+ "createdAt",
+ "updatedAt",
+ "title",
+ "content",
+ "articles",
+ "stories",
+ "xPosts",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalNewsletterDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each value in articles (dict)
+ _field_dict = {}
+ if self.articles:
+ for _key_articles in self.articles:
+ _field_dict[_key_articles] = (
+ self.articles[_key_articles].to_dict()
+ if self.articles[_key_articles] is not None
+ else None
+ )
+ _dict["articles"] = _field_dict
+ # override the default output from pydantic by calling `to_dict()` of each value in stories (dict)
+ _field_dict = {}
+ if self.stories:
+ for _key_stories in self.stories:
+ _field_dict[_key_stories] = (
+ self.stories[_key_stories].to_dict()
+ if self.stories[_key_stories] is not None
+ else None
+ )
+ _dict["stories"] = _field_dict
+ # override the default output from pydantic by calling `to_dict()` of each value in x_posts (dict)
+ _field_dict = {}
+ if self.x_posts:
+ for _key_x_posts in self.x_posts:
+ _field_dict[_key_x_posts] = (
+ self.x_posts[_key_x_posts].to_dict()
+ if self.x_posts[_key_x_posts] is not None
+ else None
+ )
+ _dict["xPosts"] = _field_dict
+ # set to None if uuid (nullable) is None
+ # and model_fields_set contains the field
+ if self.uuid is None and "uuid" in self.model_fields_set:
+ _dict["uuid"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if content (nullable) is None
+ # and model_fields_set contains the field
+ if self.content is None and "content" in self.model_fields_set:
+ _dict["content"] = None
+
+ # set to None if articles (nullable) is None
+ # and model_fields_set contains the field
+ if self.articles is None and "articles" in self.model_fields_set:
+ _dict["articles"] = None
+
+ # set to None if stories (nullable) is None
+ # and model_fields_set contains the field
+ if self.stories is None and "stories" in self.model_fields_set:
+ _dict["stories"] = None
+
+ # set to None if x_posts (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_posts is None and "x_posts" in self.model_fields_set:
+ _dict["xPosts"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalNewsletterDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "uuid": obj.get("uuid"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "title": obj.get("title"),
+ "content": obj.get("content"),
+ "articles": dict(
+ (_k, SignalArticlePartialDto.from_dict(_v))
+ for _k, _v in obj["articles"].items()
+ )
+ if obj.get("articles") is not None
+ else None,
+ "stories": dict(
+ (_k, SignalNewsletterStoryPartialDto.from_dict(_v))
+ for _k, _v in obj["stories"].items()
+ )
+ if obj.get("stories") is not None
+ else None,
+ "xPosts": dict(
+ (_k, XPost.from_dict(_v)) for _k, _v in obj["xPosts"].items()
+ )
+ if obj.get("xPosts") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_newsletter_story_partial_dto.py b/src/perigon/models/signal_newsletter_story_partial_dto.py
new file mode 100644
index 0000000..fd14394
--- /dev/null
+++ b/src/perigon/models/signal_newsletter_story_partial_dto.py
@@ -0,0 +1,150 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalNewsletterStoryPartialDto(BaseModel):
+ """
+ Story details keyed by story identifier for stories cited in the newsletter.
+ """
+
+ slug: Optional[StrictStr] = Field(
+ default=None, description="URL-safe identifier for the story."
+ )
+ url: Optional[StrictStr] = Field(default=None, description="URL of the story.")
+ title: Optional[StrictStr] = Field(default=None, description="Story headline.")
+ source_favicon: Optional[StrictStr] = Field(
+ default=None,
+ description="URL of the source favicon used for the story.",
+ alias="sourceFavicon",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the story was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the story was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ __properties: ClassVar[List[str]] = [
+ "slug",
+ "url",
+ "title",
+ "sourceFavicon",
+ "updatedAt",
+ "createdAt",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalNewsletterStoryPartialDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if slug (nullable) is None
+ # and model_fields_set contains the field
+ if self.slug is None and "slug" in self.model_fields_set:
+ _dict["slug"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if source_favicon (nullable) is None
+ # and model_fields_set contains the field
+ if self.source_favicon is None and "source_favicon" in self.model_fields_set:
+ _dict["sourceFavicon"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalNewsletterStoryPartialDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "slug": obj.get("slug"),
+ "url": obj.get("url"),
+ "title": obj.get("title"),
+ "sourceFavicon": obj.get("sourceFavicon"),
+ "updatedAt": obj.get("updatedAt"),
+ "createdAt": obj.get("createdAt"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_reddit_query.py b/src/perigon/models/signal_reddit_query.py
new file mode 100644
index 0000000..a73f4d5
--- /dev/null
+++ b/src/perigon/models/signal_reddit_query.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalRedditQuery(BaseModel):
+ """
+ SignalRedditQuery
+ """
+
+ version: StrictStr
+ query: StrictStr
+ subreddit: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = ["version", "query", "subreddit"]
+
+ @field_validator("version")
+ def version_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["V1"]):
+ raise ValueError("must be one of enum values ('V1')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalRedditQuery from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if subreddit (nullable) is None
+ # and model_fields_set contains the field
+ if self.subreddit is None and "subreddit" in self.model_fields_set:
+ _dict["subreddit"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalRedditQuery from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "version": obj.get("version"),
+ "query": obj.get("query"),
+ "subreddit": obj.get("subreddit"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/illegal_parameter_exception_suppressed_inner.py b/src/perigon/models/signal_schedule_policy.py
similarity index 55%
rename from perigon/models/illegal_parameter_exception_suppressed_inner.py
rename to src/perigon/models/signal_schedule_policy.py
index c87b356..fdec75e 100644
--- a/perigon/models/illegal_parameter_exception_suppressed_inner.py
+++ b/src/perigon/models/signal_schedule_policy.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,29 +20,32 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
-from typing_extensions import Self
+from pydantic_core import to_jsonable_python
+from typing_extensions import Annotated, Self
-from perigon.models.illegal_parameter_exception_cause_stack_trace_inner import (
- IllegalParameterExceptionCauseStackTraceInner,
-)
+from perigon.models.schedule_interval import ScheduleInterval
-class IllegalParameterExceptionSuppressedInner(BaseModel):
+class SignalSchedulePolicy(BaseModel):
+ """
+ Recurring processing schedule configured for the monitor.
"""
- IllegalParameterExceptionSuppressedInner
- """ # noqa: E501
- stack_trace: Optional[List[IllegalParameterExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
+ intervals: Annotated[List[ScheduleInterval], Field(min_length=1, max_length=24)] = (
+ Field(
+ description="Times when the monitor runs. Must contain between 1 and 24 intervals."
+ )
)
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
+ timezone_id: Optional[StrictStr] = Field(
+ default=None,
+ description="IANA time zone used to interpret schedule intervals, such as America/Chicago. Defaults to UTC.",
+ alias="timezoneId",
)
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
+ __properties: ClassVar[List[str]] = ["intervals", "timezoneId"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,12 +56,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionSuppressedInner from a JSON string"""
+ """Create an instance of SignalSchedulePolicy from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -79,18 +80,24 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
+ # override the default output from pydantic by calling `to_dict()` of each item in intervals (list)
_items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
+ if self.intervals:
+ for _item_intervals in self.intervals:
+ _items.append(
+ _item_intervals.to_dict() if _item_intervals is not None else None
+ )
+ _dict["intervals"] = _items
+ # set to None if timezone_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.timezone_id is None and "timezone_id" in self.model_fields_set:
+ _dict["timezoneId"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionSuppressedInner from a dict"""
+ """Create an instance of SignalSchedulePolicy from a dict"""
if obj is None:
return None
@@ -99,16 +106,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "stackTrace": (
- [
- IllegalParameterExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
+ "intervals": [
+ ScheduleInterval.from_dict(_item) for _item in obj["intervals"]
+ ]
+ if obj.get("intervals") is not None
+ else None,
+ "timezoneId": obj.get("timezoneId"),
}
)
return _obj
diff --git a/src/perigon/models/signal_summary_dto.py b/src/perigon/models/signal_summary_dto.py
new file mode 100644
index 0000000..f0e3bcd
--- /dev/null
+++ b/src/perigon/models/signal_summary_dto.py
@@ -0,0 +1,119 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalSummaryDto(BaseModel):
+ """
+ Summaries returned for the requested page.
+ """
+
+ text: Optional[StrictStr] = Field(
+ default=None,
+ description="AI-generated summary of the monitor's matching content.",
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the summary was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the summary was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ __properties: ClassVar[List[str]] = ["text", "createdAt", "updatedAt"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalSummaryDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalSummaryDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "text": obj.get("text"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/signal_x_query.py b/src/perigon/models/signal_x_query.py
new file mode 100644
index 0000000..dbb5ca5
--- /dev/null
+++ b/src/perigon/models/signal_x_query.py
@@ -0,0 +1,106 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SignalXQuery(BaseModel):
+ """
+ SignalXQuery
+ """
+
+ version: StrictStr
+ query: StrictStr
+ search_type: StrictStr = Field(alias="searchType")
+ __properties: ClassVar[List[str]] = ["version", "query", "searchType"]
+
+ @field_validator("version")
+ def version_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["V1"]):
+ raise ValueError("must be one of enum values ('V1')")
+ return value
+
+ @field_validator("search_type")
+ def search_type_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(["TOP", "LATEST", "MEDIA"]):
+ raise ValueError("must be one of enum values ('TOP', 'LATEST', 'MEDIA')")
+ return value
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SignalXQuery from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SignalXQuery from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "version": obj.get("version"),
+ "query": obj.get("query"),
+ "searchType": obj.get("searchType"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/social_linked_in_feed_post.py b/src/perigon/models/social_linked_in_feed_post.py
new file mode 100644
index 0000000..91b356f
--- /dev/null
+++ b/src/perigon/models/social_linked_in_feed_post.py
@@ -0,0 +1,284 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.social_linked_in_feed_post_author import (
+ SocialLinkedInFeedPostAuthor,
+)
+from perigon.models.social_linked_in_feed_post_comment import (
+ SocialLinkedInFeedPostComment,
+)
+from perigon.models.social_linked_in_feed_post_reaction import (
+ SocialLinkedInFeedPostReaction,
+)
+
+
+class SocialLinkedInFeedPost(BaseModel):
+ """
+ An item of a LinkedIn person's activity or a company's posts.
+ """
+
+ activity_type: Optional[StrictStr] = Field(
+ default=None,
+ description="Kind of person activity: `Post`, `Repost`, `Comment`, `Reply` or `Reaction`. Empty for company posts.",
+ alias="activityType",
+ )
+ urn: Optional[StrictStr] = None
+ url: Optional[StrictStr] = None
+ share_url: Optional[StrictStr] = Field(default=None, alias="shareUrl")
+ text: Optional[StrictStr] = None
+ created_at: Optional[datetime] = Field(default=None, alias="createdAt")
+ is_repost: Optional[StrictBool] = Field(default=None, alias="isRepost")
+ likes: Optional[StrictInt] = None
+ comments: Optional[StrictInt] = None
+ shares: Optional[StrictInt] = None
+ reactions: Optional[List[SocialLinkedInFeedPostReaction]] = None
+ attachment_types: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="Types of the post's attachments, e.g. `image` or `video`.",
+ alias="attachmentTypes",
+ )
+ author: Optional[SocialLinkedInFeedPostAuthor] = None
+ shared_post: Optional[SocialLinkedInFeedPost] = Field(
+ default=None, alias="sharedPost"
+ )
+ original_post_url: Optional[StrictStr] = Field(
+ default=None, alias="originalPostUrl"
+ )
+ comment: Optional[SocialLinkedInFeedPostComment] = None
+ reaction_text: Optional[StrictStr] = Field(default=None, alias="reactionText")
+ __properties: ClassVar[List[str]] = [
+ "activityType",
+ "urn",
+ "url",
+ "shareUrl",
+ "text",
+ "createdAt",
+ "isRepost",
+ "likes",
+ "comments",
+ "shares",
+ "reactions",
+ "attachmentTypes",
+ "author",
+ "sharedPost",
+ "originalPostUrl",
+ "comment",
+ "reactionText",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPost from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in reactions (list)
+ _items = []
+ if self.reactions:
+ for _item_reactions in self.reactions:
+ _items.append(
+ _item_reactions.to_dict() if _item_reactions is not None else None
+ )
+ _dict["reactions"] = _items
+ # override the default output from pydantic by calling `to_dict()` of author
+ if self.author:
+ _dict["author"] = self.author.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of shared_post
+ if self.shared_post:
+ _dict["sharedPost"] = self.shared_post.to_dict()
+ # override the default output from pydantic by calling `to_dict()` of comment
+ if self.comment:
+ _dict["comment"] = self.comment.to_dict()
+ # set to None if activity_type (nullable) is None
+ # and model_fields_set contains the field
+ if self.activity_type is None and "activity_type" in self.model_fields_set:
+ _dict["activityType"] = None
+
+ # set to None if urn (nullable) is None
+ # and model_fields_set contains the field
+ if self.urn is None and "urn" in self.model_fields_set:
+ _dict["urn"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if share_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.share_url is None and "share_url" in self.model_fields_set:
+ _dict["shareUrl"] = None
+
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if is_repost (nullable) is None
+ # and model_fields_set contains the field
+ if self.is_repost is None and "is_repost" in self.model_fields_set:
+ _dict["isRepost"] = None
+
+ # set to None if likes (nullable) is None
+ # and model_fields_set contains the field
+ if self.likes is None and "likes" in self.model_fields_set:
+ _dict["likes"] = None
+
+ # set to None if comments (nullable) is None
+ # and model_fields_set contains the field
+ if self.comments is None and "comments" in self.model_fields_set:
+ _dict["comments"] = None
+
+ # set to None if shares (nullable) is None
+ # and model_fields_set contains the field
+ if self.shares is None and "shares" in self.model_fields_set:
+ _dict["shares"] = None
+
+ # set to None if reactions (nullable) is None
+ # and model_fields_set contains the field
+ if self.reactions is None and "reactions" in self.model_fields_set:
+ _dict["reactions"] = None
+
+ # set to None if attachment_types (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.attachment_types is None
+ and "attachment_types" in self.model_fields_set
+ ):
+ _dict["attachmentTypes"] = None
+
+ # set to None if author (nullable) is None
+ # and model_fields_set contains the field
+ if self.author is None and "author" in self.model_fields_set:
+ _dict["author"] = None
+
+ # set to None if shared_post (nullable) is None
+ # and model_fields_set contains the field
+ if self.shared_post is None and "shared_post" in self.model_fields_set:
+ _dict["sharedPost"] = None
+
+ # set to None if original_post_url (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.original_post_url is None
+ and "original_post_url" in self.model_fields_set
+ ):
+ _dict["originalPostUrl"] = None
+
+ # set to None if comment (nullable) is None
+ # and model_fields_set contains the field
+ if self.comment is None and "comment" in self.model_fields_set:
+ _dict["comment"] = None
+
+ # set to None if reaction_text (nullable) is None
+ # and model_fields_set contains the field
+ if self.reaction_text is None and "reaction_text" in self.model_fields_set:
+ _dict["reactionText"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPost from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "activityType": obj.get("activityType"),
+ "urn": obj.get("urn"),
+ "url": obj.get("url"),
+ "shareUrl": obj.get("shareUrl"),
+ "text": obj.get("text"),
+ "createdAt": obj.get("createdAt"),
+ "isRepost": obj.get("isRepost"),
+ "likes": obj.get("likes"),
+ "comments": obj.get("comments"),
+ "shares": obj.get("shares"),
+ "reactions": [
+ SocialLinkedInFeedPostReaction.from_dict(_item)
+ for _item in obj["reactions"]
+ ]
+ if obj.get("reactions") is not None
+ else None,
+ "attachmentTypes": obj.get("attachmentTypes"),
+ "author": SocialLinkedInFeedPostAuthor.from_dict(obj["author"])
+ if obj.get("author") is not None
+ else None,
+ "sharedPost": SocialLinkedInFeedPost.from_dict(obj["sharedPost"])
+ if obj.get("sharedPost") is not None
+ else None,
+ "originalPostUrl": obj.get("originalPostUrl"),
+ "comment": SocialLinkedInFeedPostComment.from_dict(obj["comment"])
+ if obj.get("comment") is not None
+ else None,
+ "reactionText": obj.get("reactionText"),
+ }
+ )
+ return _obj
+
+
+# TODO: Rewrite to not use raise_errors
+SocialLinkedInFeedPost.model_rebuild(raise_errors=False)
diff --git a/src/perigon/models/social_linked_in_feed_post_author.py b/src/perigon/models/social_linked_in_feed_post_author.py
new file mode 100644
index 0000000..637dc52
--- /dev/null
+++ b/src/perigon/models/social_linked_in_feed_post_author.py
@@ -0,0 +1,142 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SocialLinkedInFeedPostAuthor(BaseModel):
+ """
+ SocialLinkedInFeedPostAuthor
+ """
+
+ name: Optional[StrictStr] = Field(
+ default=None, description="Person or company display name."
+ )
+ public_identifier: Optional[StrictStr] = Field(
+ default=None, alias="publicIdentifier"
+ )
+ occupation: Optional[StrictStr] = None
+ type: Optional[StrictStr] = None
+ url: Optional[StrictStr] = None
+ followers: Optional[StrictInt] = None
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "publicIdentifier",
+ "occupation",
+ "type",
+ "url",
+ "followers",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPostAuthor from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if public_identifier (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.public_identifier is None
+ and "public_identifier" in self.model_fields_set
+ ):
+ _dict["publicIdentifier"] = None
+
+ # set to None if occupation (nullable) is None
+ # and model_fields_set contains the field
+ if self.occupation is None and "occupation" in self.model_fields_set:
+ _dict["occupation"] = None
+
+ # set to None if type (nullable) is None
+ # and model_fields_set contains the field
+ if self.type is None and "type" in self.model_fields_set:
+ _dict["type"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if followers (nullable) is None
+ # and model_fields_set contains the field
+ if self.followers is None and "followers" in self.model_fields_set:
+ _dict["followers"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPostAuthor from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "publicIdentifier": obj.get("publicIdentifier"),
+ "occupation": obj.get("occupation"),
+ "type": obj.get("type"),
+ "url": obj.get("url"),
+ "followers": obj.get("followers"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/too_many_requests_exception_cause.py b/src/perigon/models/social_linked_in_feed_post_comment.py
similarity index 51%
rename from perigon/models/too_many_requests_exception_cause.py
rename to src/perigon/models/social_linked_in_feed_post_comment.py
index 6ac1464..df14bd8 100644
--- a/perigon/models/too_many_requests_exception_cause.py
+++ b/src/perigon/models/social_linked_in_feed_post_comment.py
@@ -10,40 +10,36 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
import json
import pprint
import re # noqa: F401
+from datetime import datetime
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.too_many_requests_exception_cause_stack_trace_inner import (
- TooManyRequestsExceptionCauseStackTraceInner,
-)
-
-class TooManyRequestsExceptionCause(BaseModel):
+class SocialLinkedInFeedPostComment(BaseModel):
+ """
+ The person's comment for `Comment` and `Reply` activity.
"""
- TooManyRequestsExceptionCause
- """ # noqa: E501
- stack_trace: Optional[List[TooManyRequestsExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
+ urn: Optional[StrictStr] = None
+ url: Optional[StrictStr] = None
+ text: Optional[StrictStr] = None
+ created_at: Optional[datetime] = Field(default=None, alias="createdAt")
+ likes: Optional[StrictInt] = None
+ __properties: ClassVar[List[str]] = ["urn", "url", "text", "createdAt", "likes"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,12 +50,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionCause from a JSON string"""
+ """Create an instance of SocialLinkedInFeedPostComment from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -79,18 +74,36 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
- _items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
+ # set to None if urn (nullable) is None
+ # and model_fields_set contains the field
+ if self.urn is None and "urn" in self.model_fields_set:
+ _dict["urn"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if likes (nullable) is None
+ # and model_fields_set contains the field
+ if self.likes is None and "likes" in self.model_fields_set:
+ _dict["likes"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of TooManyRequestsExceptionCause from a dict"""
+ """Create an instance of SocialLinkedInFeedPostComment from a dict"""
if obj is None:
return None
@@ -99,16 +112,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "stackTrace": (
- [
- TooManyRequestsExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
+ "urn": obj.get("urn"),
+ "url": obj.get("url"),
+ "text": obj.get("text"),
+ "createdAt": obj.get("createdAt"),
+ "likes": obj.get("likes"),
}
)
return _obj
diff --git a/src/perigon/models/social_linked_in_feed_post_reaction.py b/src/perigon/models/social_linked_in_feed_post_reaction.py
new file mode 100644
index 0000000..da05693
--- /dev/null
+++ b/src/perigon/models/social_linked_in_feed_post_reaction.py
@@ -0,0 +1,92 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SocialLinkedInFeedPostReaction(BaseModel):
+ """
+ SocialLinkedInFeedPostReaction
+ """
+
+ reaction_type: StrictStr = Field(alias="reactionType")
+ count: Optional[StrictInt] = None
+ __properties: ClassVar[List[str]] = ["reactionType", "count"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPostReaction from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if count (nullable) is None
+ # and model_fields_set contains the field
+ if self.count is None and "count" in self.model_fields_set:
+ _dict["count"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedPostReaction from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {"reactionType": obj.get("reactionType"), "count": obj.get("count")}
+ )
+ return _obj
diff --git a/src/perigon/models/social_linked_in_feed_result.py b/src/perigon/models/social_linked_in_feed_result.py
new file mode 100644
index 0000000..0f43683
--- /dev/null
+++ b/src/perigon/models/social_linked_in_feed_result.py
@@ -0,0 +1,118 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.social_linked_in_feed_post import SocialLinkedInFeedPost
+
+
+class SocialLinkedInFeedResult(BaseModel):
+ """
+ SocialLinkedInFeedResult
+ """
+
+ status: StrictInt
+ num_results: StrictInt = Field(alias="numResults")
+ results: List[SocialLinkedInFeedPost]
+ next_cursor: Optional[StrictStr] = Field(default=None, alias="nextCursor")
+ __properties: ClassVar[List[str]] = [
+ "status",
+ "numResults",
+ "results",
+ "nextCursor",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in results (list)
+ _items = []
+ if self.results:
+ for _item_results in self.results:
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
+ _dict["results"] = _items
+ # set to None if next_cursor (nullable) is None
+ # and model_fields_set contains the field
+ if self.next_cursor is None and "next_cursor" in self.model_fields_set:
+ _dict["nextCursor"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInFeedResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "status": obj.get("status"),
+ "numResults": obj.get("numResults"),
+ "results": [
+ SocialLinkedInFeedPost.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
+ "nextCursor": obj.get("nextCursor"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/social_linked_in_post.py b/src/perigon/models/social_linked_in_post.py
new file mode 100644
index 0000000..a5d4c83
--- /dev/null
+++ b/src/perigon/models/social_linked_in_post.py
@@ -0,0 +1,210 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.social_linked_in_post_author import SocialLinkedInPostAuthor
+
+
+class SocialLinkedInPost(BaseModel):
+ """
+ A LinkedIn post found by keyword search.
+ """
+
+ urn: StrictStr
+ url: Optional[StrictStr] = None
+ text: Optional[StrictStr] = None
+ posted_at: Optional[datetime] = Field(default=None, alias="postedAt")
+ reposted: Optional[StrictBool] = None
+ author: Optional[SocialLinkedInPostAuthor] = None
+ reactions: Optional[StrictInt] = None
+ likes: Optional[StrictInt] = None
+ empathy: Optional[StrictInt] = None
+ appreciation: Optional[StrictInt] = None
+ praise: Optional[StrictInt] = None
+ interest: Optional[StrictInt] = None
+ funny: Optional[StrictInt] = None
+ maybe: Optional[StrictInt] = None
+ comments: Optional[StrictInt] = None
+ __properties: ClassVar[List[str]] = [
+ "urn",
+ "url",
+ "text",
+ "postedAt",
+ "reposted",
+ "author",
+ "reactions",
+ "likes",
+ "empathy",
+ "appreciation",
+ "praise",
+ "interest",
+ "funny",
+ "maybe",
+ "comments",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInPost from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of author
+ if self.author:
+ _dict["author"] = self.author.to_dict()
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if posted_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.posted_at is None and "posted_at" in self.model_fields_set:
+ _dict["postedAt"] = None
+
+ # set to None if reposted (nullable) is None
+ # and model_fields_set contains the field
+ if self.reposted is None and "reposted" in self.model_fields_set:
+ _dict["reposted"] = None
+
+ # set to None if author (nullable) is None
+ # and model_fields_set contains the field
+ if self.author is None and "author" in self.model_fields_set:
+ _dict["author"] = None
+
+ # set to None if reactions (nullable) is None
+ # and model_fields_set contains the field
+ if self.reactions is None and "reactions" in self.model_fields_set:
+ _dict["reactions"] = None
+
+ # set to None if likes (nullable) is None
+ # and model_fields_set contains the field
+ if self.likes is None and "likes" in self.model_fields_set:
+ _dict["likes"] = None
+
+ # set to None if empathy (nullable) is None
+ # and model_fields_set contains the field
+ if self.empathy is None and "empathy" in self.model_fields_set:
+ _dict["empathy"] = None
+
+ # set to None if appreciation (nullable) is None
+ # and model_fields_set contains the field
+ if self.appreciation is None and "appreciation" in self.model_fields_set:
+ _dict["appreciation"] = None
+
+ # set to None if praise (nullable) is None
+ # and model_fields_set contains the field
+ if self.praise is None and "praise" in self.model_fields_set:
+ _dict["praise"] = None
+
+ # set to None if interest (nullable) is None
+ # and model_fields_set contains the field
+ if self.interest is None and "interest" in self.model_fields_set:
+ _dict["interest"] = None
+
+ # set to None if funny (nullable) is None
+ # and model_fields_set contains the field
+ if self.funny is None and "funny" in self.model_fields_set:
+ _dict["funny"] = None
+
+ # set to None if maybe (nullable) is None
+ # and model_fields_set contains the field
+ if self.maybe is None and "maybe" in self.model_fields_set:
+ _dict["maybe"] = None
+
+ # set to None if comments (nullable) is None
+ # and model_fields_set contains the field
+ if self.comments is None and "comments" in self.model_fields_set:
+ _dict["comments"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInPost from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "urn": obj.get("urn"),
+ "url": obj.get("url"),
+ "text": obj.get("text"),
+ "postedAt": obj.get("postedAt"),
+ "reposted": obj.get("reposted"),
+ "author": SocialLinkedInPostAuthor.from_dict(obj["author"])
+ if obj.get("author") is not None
+ else None,
+ "reactions": obj.get("reactions"),
+ "likes": obj.get("likes"),
+ "empathy": obj.get("empathy"),
+ "appreciation": obj.get("appreciation"),
+ "praise": obj.get("praise"),
+ "interest": obj.get("interest"),
+ "funny": obj.get("funny"),
+ "maybe": obj.get("maybe"),
+ "comments": obj.get("comments"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/social_linked_in_post_author.py b/src/perigon/models/social_linked_in_post_author.py
new file mode 100644
index 0000000..e5c3f8f
--- /dev/null
+++ b/src/perigon/models/social_linked_in_post_author.py
@@ -0,0 +1,152 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SocialLinkedInPostAuthor(BaseModel):
+ """
+ SocialLinkedInPostAuthor
+ """
+
+ id: Optional[StrictStr] = None
+ full_name: Optional[StrictStr] = Field(default=None, alias="fullName")
+ username: Optional[StrictStr] = None
+ type: Optional[StrictStr] = Field(
+ default=None, description="Author kind, e.g. `person` or `company`."
+ )
+ url: Optional[StrictStr] = None
+ urn: Optional[StrictStr] = None
+ profile_picture_url: Optional[StrictStr] = Field(
+ default=None,
+ description="URL of the largest available profile picture.",
+ alias="profilePictureUrl",
+ )
+ __properties: ClassVar[List[str]] = [
+ "id",
+ "fullName",
+ "username",
+ "type",
+ "url",
+ "urn",
+ "profilePictureUrl",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialLinkedInPostAuthor from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if id (nullable) is None
+ # and model_fields_set contains the field
+ if self.id is None and "id" in self.model_fields_set:
+ _dict["id"] = None
+
+ # set to None if full_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.full_name is None and "full_name" in self.model_fields_set:
+ _dict["fullName"] = None
+
+ # set to None if username (nullable) is None
+ # and model_fields_set contains the field
+ if self.username is None and "username" in self.model_fields_set:
+ _dict["username"] = None
+
+ # set to None if type (nullable) is None
+ # and model_fields_set contains the field
+ if self.type is None and "type" in self.model_fields_set:
+ _dict["type"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if urn (nullable) is None
+ # and model_fields_set contains the field
+ if self.urn is None and "urn" in self.model_fields_set:
+ _dict["urn"] = None
+
+ # set to None if profile_picture_url (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.profile_picture_url is None
+ and "profile_picture_url" in self.model_fields_set
+ ):
+ _dict["profilePictureUrl"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialLinkedInPostAuthor from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "id": obj.get("id"),
+ "fullName": obj.get("fullName"),
+ "username": obj.get("username"),
+ "type": obj.get("type"),
+ "url": obj.get("url"),
+ "urn": obj.get("urn"),
+ "profilePictureUrl": obj.get("profilePictureUrl"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/illegal_parameter_exception_cause.py b/src/perigon/models/social_linked_in_post_search_result.py
similarity index 57%
rename from perigon/models/illegal_parameter_exception_cause.py
rename to src/perigon/models/social_linked_in_post_search_result.py
index d185cb5..bf8b559 100644
--- a/perigon/models/illegal_parameter_exception_cause.py
+++ b/src/perigon/models/social_linked_in_post_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,30 +19,27 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.illegal_parameter_exception_cause_stack_trace_inner import (
- IllegalParameterExceptionCauseStackTraceInner,
-)
+from perigon.models.social_linked_in_post import SocialLinkedInPost
-class IllegalParameterExceptionCause(BaseModel):
+class SocialLinkedInPostSearchResult(BaseModel):
+ """
+ SocialLinkedInPostSearchResult
"""
- IllegalParameterExceptionCause
- """ # noqa: E501
- stack_trace: Optional[List[IllegalParameterExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
+ status: StrictInt
+ num_results: StrictInt = Field(alias="numResults")
+ results: List[SocialLinkedInPost]
+ next_page: Optional[StrictInt] = Field(default=None, alias="nextPage")
+ __properties: ClassVar[List[str]] = ["status", "numResults", "results", "nextPage"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,12 +50,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionCause from a JSON string"""
+ """Create an instance of SocialLinkedInPostSearchResult from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -79,18 +74,24 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
+ # override the default output from pydantic by calling `to_dict()` of each item in results (list)
_items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
+ if self.results:
+ for _item_results in self.results:
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
+ _dict["results"] = _items
+ # set to None if next_page (nullable) is None
+ # and model_fields_set contains the field
+ if self.next_page is None and "next_page" in self.model_fields_set:
+ _dict["nextPage"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of IllegalParameterExceptionCause from a dict"""
+ """Create an instance of SocialLinkedInPostSearchResult from a dict"""
if obj is None:
return None
@@ -99,16 +100,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "stackTrace": (
- [
- IllegalParameterExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
+ "status": obj.get("status"),
+ "numResults": obj.get("numResults"),
+ "results": [
+ SocialLinkedInPost.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
+ "nextPage": obj.get("nextPage"),
}
)
return _obj
diff --git a/src/perigon/models/social_reddit_post.py b/src/perigon/models/social_reddit_post.py
new file mode 100644
index 0000000..a48055c
--- /dev/null
+++ b/src/perigon/models/social_reddit_post.py
@@ -0,0 +1,338 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set, Union
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ StrictBool,
+ StrictFloat,
+ StrictInt,
+ StrictStr,
+)
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SocialRedditPost(BaseModel):
+ """
+ A Reddit post.
+ """
+
+ id: StrictStr
+ fullname: Optional[StrictStr] = Field(
+ default=None, description="Reddit fullname, e.g. `t3_abc123`."
+ )
+ title: Optional[StrictStr] = None
+ text: Optional[StrictStr] = None
+ author: Optional[StrictStr] = None
+ subreddit: Optional[StrictStr] = None
+ subreddit_prefixed: Optional[StrictStr] = Field(
+ default=None, alias="subredditPrefixed"
+ )
+ subreddit_subscribers: Optional[StrictInt] = Field(
+ default=None, alias="subredditSubscribers"
+ )
+ url: Optional[StrictStr] = Field(default=None, description="Permalink of the post.")
+ link_url: Optional[StrictStr] = Field(
+ default=None,
+ description="Link the post points to, for link posts.",
+ alias="linkUrl",
+ )
+ domain: Optional[StrictStr] = None
+ flair: Optional[StrictStr] = None
+ post_hint: Optional[StrictStr] = Field(default=None, alias="postHint")
+ score: Optional[StrictInt] = None
+ upvotes: Optional[StrictInt] = None
+ upvote_ratio: Optional[Union[StrictFloat, StrictInt]] = Field(
+ default=None, alias="upvoteRatio"
+ )
+ num_comments: Optional[StrictInt] = Field(default=None, alias="numComments")
+ num_crossposts: Optional[StrictInt] = Field(default=None, alias="numCrossposts")
+ total_awards_received: Optional[StrictInt] = Field(
+ default=None, alias="totalAwardsReceived"
+ )
+ created_at: Optional[datetime] = Field(default=None, alias="createdAt")
+ is_video: Optional[StrictBool] = Field(default=None, alias="isVideo")
+ is_self: Optional[StrictBool] = Field(default=None, alias="isSelf")
+ over18: Optional[StrictBool] = None
+ spoiler: Optional[StrictBool] = None
+ stickied: Optional[StrictBool] = None
+ locked: Optional[StrictBool] = None
+ archived: Optional[StrictBool] = None
+ thumbnail: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = [
+ "id",
+ "fullname",
+ "title",
+ "text",
+ "author",
+ "subreddit",
+ "subredditPrefixed",
+ "subredditSubscribers",
+ "url",
+ "linkUrl",
+ "domain",
+ "flair",
+ "postHint",
+ "score",
+ "upvotes",
+ "upvoteRatio",
+ "numComments",
+ "numCrossposts",
+ "totalAwardsReceived",
+ "createdAt",
+ "isVideo",
+ "isSelf",
+ "over18",
+ "spoiler",
+ "stickied",
+ "locked",
+ "archived",
+ "thumbnail",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialRedditPost from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if fullname (nullable) is None
+ # and model_fields_set contains the field
+ if self.fullname is None and "fullname" in self.model_fields_set:
+ _dict["fullname"] = None
+
+ # set to None if title (nullable) is None
+ # and model_fields_set contains the field
+ if self.title is None and "title" in self.model_fields_set:
+ _dict["title"] = None
+
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if author (nullable) is None
+ # and model_fields_set contains the field
+ if self.author is None and "author" in self.model_fields_set:
+ _dict["author"] = None
+
+ # set to None if subreddit (nullable) is None
+ # and model_fields_set contains the field
+ if self.subreddit is None and "subreddit" in self.model_fields_set:
+ _dict["subreddit"] = None
+
+ # set to None if subreddit_prefixed (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.subreddit_prefixed is None
+ and "subreddit_prefixed" in self.model_fields_set
+ ):
+ _dict["subredditPrefixed"] = None
+
+ # set to None if subreddit_subscribers (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.subreddit_subscribers is None
+ and "subreddit_subscribers" in self.model_fields_set
+ ):
+ _dict["subredditSubscribers"] = None
+
+ # set to None if url (nullable) is None
+ # and model_fields_set contains the field
+ if self.url is None and "url" in self.model_fields_set:
+ _dict["url"] = None
+
+ # set to None if link_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.link_url is None and "link_url" in self.model_fields_set:
+ _dict["linkUrl"] = None
+
+ # set to None if domain (nullable) is None
+ # and model_fields_set contains the field
+ if self.domain is None and "domain" in self.model_fields_set:
+ _dict["domain"] = None
+
+ # set to None if flair (nullable) is None
+ # and model_fields_set contains the field
+ if self.flair is None and "flair" in self.model_fields_set:
+ _dict["flair"] = None
+
+ # set to None if post_hint (nullable) is None
+ # and model_fields_set contains the field
+ if self.post_hint is None and "post_hint" in self.model_fields_set:
+ _dict["postHint"] = None
+
+ # set to None if score (nullable) is None
+ # and model_fields_set contains the field
+ if self.score is None and "score" in self.model_fields_set:
+ _dict["score"] = None
+
+ # set to None if upvotes (nullable) is None
+ # and model_fields_set contains the field
+ if self.upvotes is None and "upvotes" in self.model_fields_set:
+ _dict["upvotes"] = None
+
+ # set to None if upvote_ratio (nullable) is None
+ # and model_fields_set contains the field
+ if self.upvote_ratio is None and "upvote_ratio" in self.model_fields_set:
+ _dict["upvoteRatio"] = None
+
+ # set to None if num_comments (nullable) is None
+ # and model_fields_set contains the field
+ if self.num_comments is None and "num_comments" in self.model_fields_set:
+ _dict["numComments"] = None
+
+ # set to None if num_crossposts (nullable) is None
+ # and model_fields_set contains the field
+ if self.num_crossposts is None and "num_crossposts" in self.model_fields_set:
+ _dict["numCrossposts"] = None
+
+ # set to None if total_awards_received (nullable) is None
+ # and model_fields_set contains the field
+ if (
+ self.total_awards_received is None
+ and "total_awards_received" in self.model_fields_set
+ ):
+ _dict["totalAwardsReceived"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if is_video (nullable) is None
+ # and model_fields_set contains the field
+ if self.is_video is None and "is_video" in self.model_fields_set:
+ _dict["isVideo"] = None
+
+ # set to None if is_self (nullable) is None
+ # and model_fields_set contains the field
+ if self.is_self is None and "is_self" in self.model_fields_set:
+ _dict["isSelf"] = None
+
+ # set to None if over18 (nullable) is None
+ # and model_fields_set contains the field
+ if self.over18 is None and "over18" in self.model_fields_set:
+ _dict["over18"] = None
+
+ # set to None if spoiler (nullable) is None
+ # and model_fields_set contains the field
+ if self.spoiler is None and "spoiler" in self.model_fields_set:
+ _dict["spoiler"] = None
+
+ # set to None if stickied (nullable) is None
+ # and model_fields_set contains the field
+ if self.stickied is None and "stickied" in self.model_fields_set:
+ _dict["stickied"] = None
+
+ # set to None if locked (nullable) is None
+ # and model_fields_set contains the field
+ if self.locked is None and "locked" in self.model_fields_set:
+ _dict["locked"] = None
+
+ # set to None if archived (nullable) is None
+ # and model_fields_set contains the field
+ if self.archived is None and "archived" in self.model_fields_set:
+ _dict["archived"] = None
+
+ # set to None if thumbnail (nullable) is None
+ # and model_fields_set contains the field
+ if self.thumbnail is None and "thumbnail" in self.model_fields_set:
+ _dict["thumbnail"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialRedditPost from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "id": obj.get("id"),
+ "fullname": obj.get("fullname"),
+ "title": obj.get("title"),
+ "text": obj.get("text"),
+ "author": obj.get("author"),
+ "subreddit": obj.get("subreddit"),
+ "subredditPrefixed": obj.get("subredditPrefixed"),
+ "subredditSubscribers": obj.get("subredditSubscribers"),
+ "url": obj.get("url"),
+ "linkUrl": obj.get("linkUrl"),
+ "domain": obj.get("domain"),
+ "flair": obj.get("flair"),
+ "postHint": obj.get("postHint"),
+ "score": obj.get("score"),
+ "upvotes": obj.get("upvotes"),
+ "upvoteRatio": obj.get("upvoteRatio"),
+ "numComments": obj.get("numComments"),
+ "numCrossposts": obj.get("numCrossposts"),
+ "totalAwardsReceived": obj.get("totalAwardsReceived"),
+ "createdAt": obj.get("createdAt"),
+ "isVideo": obj.get("isVideo"),
+ "isSelf": obj.get("isSelf"),
+ "over18": obj.get("over18"),
+ "spoiler": obj.get("spoiler"),
+ "stickied": obj.get("stickied"),
+ "locked": obj.get("locked"),
+ "archived": obj.get("archived"),
+ "thumbnail": obj.get("thumbnail"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/social_reddit_post_search_result.py b/src/perigon/models/social_reddit_post_search_result.py
new file mode 100644
index 0000000..7a993c7
--- /dev/null
+++ b/src/perigon/models/social_reddit_post_search_result.py
@@ -0,0 +1,118 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.social_reddit_post import SocialRedditPost
+
+
+class SocialRedditPostSearchResult(BaseModel):
+ """
+ SocialRedditPostSearchResult
+ """
+
+ status: StrictInt
+ num_results: StrictInt = Field(alias="numResults")
+ results: List[SocialRedditPost]
+ next_cursor: Optional[StrictStr] = Field(default=None, alias="nextCursor")
+ __properties: ClassVar[List[str]] = [
+ "status",
+ "numResults",
+ "results",
+ "nextCursor",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialRedditPostSearchResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in results (list)
+ _items = []
+ if self.results:
+ for _item_results in self.results:
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
+ _dict["results"] = _items
+ # set to None if next_cursor (nullable) is None
+ # and model_fields_set contains the field
+ if self.next_cursor is None and "next_cursor" in self.model_fields_set:
+ _dict["nextCursor"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialRedditPostSearchResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "status": obj.get("status"),
+ "numResults": obj.get("numResults"),
+ "results": [
+ SocialRedditPost.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
+ "nextCursor": obj.get("nextCursor"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/social_x_post.py b/src/perigon/models/social_x_post.py
new file mode 100644
index 0000000..28dfa7c
--- /dev/null
+++ b/src/perigon/models/social_x_post.py
@@ -0,0 +1,200 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.social_x_post_author import SocialXPostAuthor
+
+
+class SocialXPost(BaseModel):
+ """
+ A post on X (Twitter).
+ """
+
+ id: StrictStr
+ url: StrictStr
+ text: Optional[StrictStr] = None
+ lang: Optional[StrictStr] = Field(
+ default=None, description="BCP 47 language tag, `und` when unknown."
+ )
+ created_at: Optional[datetime] = Field(default=None, alias="createdAt")
+ author: SocialXPostAuthor
+ views: Optional[StrictInt] = None
+ likes: Optional[StrictInt] = None
+ reposts: Optional[StrictInt] = None
+ replies: Optional[StrictInt] = None
+ quotes: Optional[StrictInt] = None
+ bookmarks: Optional[StrictInt] = None
+ photo_urls: Optional[List[StrictStr]] = Field(
+ default=None, description="URLs of attached photos.", alias="photoUrls"
+ )
+ video_urls: Optional[List[StrictStr]] = Field(
+ default=None,
+ description="URLs of attached video thumbnails.",
+ alias="videoUrls",
+ )
+ __properties: ClassVar[List[str]] = [
+ "id",
+ "url",
+ "text",
+ "lang",
+ "createdAt",
+ "author",
+ "views",
+ "likes",
+ "reposts",
+ "replies",
+ "quotes",
+ "bookmarks",
+ "photoUrls",
+ "videoUrls",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SocialXPost from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of author
+ if self.author:
+ _dict["author"] = self.author.to_dict()
+ # set to None if text (nullable) is None
+ # and model_fields_set contains the field
+ if self.text is None and "text" in self.model_fields_set:
+ _dict["text"] = None
+
+ # set to None if lang (nullable) is None
+ # and model_fields_set contains the field
+ if self.lang is None and "lang" in self.model_fields_set:
+ _dict["lang"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if views (nullable) is None
+ # and model_fields_set contains the field
+ if self.views is None and "views" in self.model_fields_set:
+ _dict["views"] = None
+
+ # set to None if likes (nullable) is None
+ # and model_fields_set contains the field
+ if self.likes is None and "likes" in self.model_fields_set:
+ _dict["likes"] = None
+
+ # set to None if reposts (nullable) is None
+ # and model_fields_set contains the field
+ if self.reposts is None and "reposts" in self.model_fields_set:
+ _dict["reposts"] = None
+
+ # set to None if replies (nullable) is None
+ # and model_fields_set contains the field
+ if self.replies is None and "replies" in self.model_fields_set:
+ _dict["replies"] = None
+
+ # set to None if quotes (nullable) is None
+ # and model_fields_set contains the field
+ if self.quotes is None and "quotes" in self.model_fields_set:
+ _dict["quotes"] = None
+
+ # set to None if bookmarks (nullable) is None
+ # and model_fields_set contains the field
+ if self.bookmarks is None and "bookmarks" in self.model_fields_set:
+ _dict["bookmarks"] = None
+
+ # set to None if photo_urls (nullable) is None
+ # and model_fields_set contains the field
+ if self.photo_urls is None and "photo_urls" in self.model_fields_set:
+ _dict["photoUrls"] = None
+
+ # set to None if video_urls (nullable) is None
+ # and model_fields_set contains the field
+ if self.video_urls is None and "video_urls" in self.model_fields_set:
+ _dict["videoUrls"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SocialXPost from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "id": obj.get("id"),
+ "url": obj.get("url"),
+ "text": obj.get("text"),
+ "lang": obj.get("lang"),
+ "createdAt": obj.get("createdAt"),
+ "author": SocialXPostAuthor.from_dict(obj["author"])
+ if obj.get("author") is not None
+ else None,
+ "views": obj.get("views"),
+ "likes": obj.get("likes"),
+ "reposts": obj.get("reposts"),
+ "replies": obj.get("replies"),
+ "quotes": obj.get("quotes"),
+ "bookmarks": obj.get("bookmarks"),
+ "photoUrls": obj.get("photoUrls"),
+ "videoUrls": obj.get("videoUrls"),
+ }
+ )
+ return _obj
diff --git a/perigon/models/vector_data.py b/src/perigon/models/social_x_post_author.py
similarity index 65%
rename from perigon/models/vector_data.py
rename to src/perigon/models/social_x_post_author.py
index 2df651b..af202a2 100644
--- a/perigon/models/vector_data.py
+++ b/src/perigon/models/social_x_post_author.py
@@ -10,31 +10,32 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
import json
import pprint
import re # noqa: F401
-from typing import Any, ClassVar, Dict, List, Optional, Set, Union
+from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-class VectorData(BaseModel):
+class SocialXPostAuthor(BaseModel):
+ """
+ SocialXPostAuthor
"""
- VectorData
- """ # noqa: E501
- data: Optional[List[Union[StrictFloat, StrictInt]]] = None
- version: Optional[StrictInt] = None
- __properties: ClassVar[List[str]] = ["data", "version"]
+ screen_name: StrictStr = Field(alias="screenName")
+ avatar_url: Optional[StrictStr] = Field(default=None, alias="avatarUrl")
+ __properties: ClassVar[List[str]] = ["screenName", "avatarUrl"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,12 +46,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of VectorData from a JSON string"""
+ """Create an instance of SocialXPostAuthor from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -70,21 +70,16 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # set to None if data (nullable) is None
- # and model_fields_set contains the field
- if self.data is None and "data" in self.model_fields_set:
- _dict["data"] = None
-
- # set to None if version (nullable) is None
+ # set to None if avatar_url (nullable) is None
# and model_fields_set contains the field
- if self.version is None and "version" in self.model_fields_set:
- _dict["version"] = None
+ if self.avatar_url is None and "avatar_url" in self.model_fields_set:
+ _dict["avatarUrl"] = None
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of VectorData from a dict"""
+ """Create an instance of SocialXPostAuthor from a dict"""
if obj is None:
return None
@@ -92,6 +87,6 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate(
- {"data": obj.get("data"), "version": obj.get("version")}
+ {"screenName": obj.get("screenName"), "avatarUrl": obj.get("avatarUrl")}
)
return _obj
diff --git a/perigon/models/auth_exception_suppressed_inner.py b/src/perigon/models/social_x_post_search_result.py
similarity index 56%
rename from perigon/models/auth_exception_suppressed_inner.py
rename to src/perigon/models/social_x_post_search_result.py
index bacbf86..aa68eb8 100644
--- a/perigon/models/auth_exception_suppressed_inner.py
+++ b/src/perigon/models/social_x_post_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,30 +19,32 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.auth_exception_cause_stack_trace_inner import (
- AuthExceptionCauseStackTraceInner,
-)
+from perigon.models.social_x_post import SocialXPost
-class AuthExceptionSuppressedInner(BaseModel):
+class SocialXPostSearchResult(BaseModel):
+ """
+ SocialXPostSearchResult
"""
- AuthExceptionSuppressedInner
- """ # noqa: E501
- stack_trace: Optional[List[AuthExceptionCauseStackTraceInner]] = Field(
- default=None, alias="stackTrace"
- )
- message: Optional[StrictStr] = None
- localized_message: Optional[StrictStr] = Field(
- default=None, alias="localizedMessage"
- )
- __properties: ClassVar[List[str]] = ["stackTrace", "message", "localizedMessage"]
+ status: StrictInt
+ num_results: StrictInt = Field(alias="numResults")
+ results: List[SocialXPost]
+ next_cursor: Optional[StrictStr] = Field(default=None, alias="nextCursor")
+ __properties: ClassVar[List[str]] = [
+ "status",
+ "numResults",
+ "results",
+ "nextCursor",
+ ]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,12 +55,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of AuthExceptionSuppressedInner from a JSON string"""
+ """Create an instance of SocialXPostSearchResult from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -79,18 +79,24 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in stack_trace (list)
+ # override the default output from pydantic by calling `to_dict()` of each item in results (list)
_items = []
- if self.stack_trace:
- for _item_stack_trace in self.stack_trace:
- if _item_stack_trace:
- _items.append(_item_stack_trace.to_dict())
- _dict["stackTrace"] = _items
+ if self.results:
+ for _item_results in self.results:
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
+ _dict["results"] = _items
+ # set to None if next_cursor (nullable) is None
+ # and model_fields_set contains the field
+ if self.next_cursor is None and "next_cursor" in self.model_fields_set:
+ _dict["nextCursor"] = None
+
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of AuthExceptionSuppressedInner from a dict"""
+ """Create an instance of SocialXPostSearchResult from a dict"""
if obj is None:
return None
@@ -99,16 +105,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "stackTrace": (
- [
- AuthExceptionCauseStackTraceInner.from_dict(_item)
- for _item in obj["stackTrace"]
- ]
- if obj.get("stackTrace") is not None
- else None
- ),
- "message": obj.get("message"),
- "localizedMessage": obj.get("localizedMessage"),
+ "status": obj.get("status"),
+ "numResults": obj.get("numResults"),
+ "results": [SocialXPost.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
+ "nextCursor": obj.get("nextCursor"),
}
)
return _obj
diff --git a/perigon/models/source.py b/src/perigon/models/source.py
similarity index 84%
rename from perigon/models/source.py
rename to src/perigon/models/source.py
index 3fcf792..98fbfe1 100644
--- a/perigon/models/source.py
+++ b/src/perigon/models/source.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.image_holder import ImageHolder
@@ -31,7 +31,7 @@
class Source(BaseModel):
"""
Source
- """ # noqa: E501
+ """
id: Optional[StrictStr] = None
domain: Optional[StrictStr] = None
@@ -99,7 +99,8 @@ class Source(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -110,8 +111,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -142,29 +142,37 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.top_categories:
for _item_top_categories in self.top_categories:
- if _item_top_categories:
- _items.append(_item_top_categories.to_dict())
+ _items.append(
+ _item_top_categories.to_dict()
+ if _item_top_categories is not None
+ else None
+ )
_dict["topCategories"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_topics (list)
_items = []
if self.top_topics:
for _item_top_topics in self.top_topics:
- if _item_top_topics:
- _items.append(_item_top_topics.to_dict())
+ _items.append(
+ _item_top_topics.to_dict() if _item_top_topics is not None else None
+ )
_dict["topTopics"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_countries (list)
_items = []
if self.top_countries:
for _item_top_countries in self.top_countries:
- if _item_top_countries:
- _items.append(_item_top_countries.to_dict())
+ _items.append(
+ _item_top_countries.to_dict()
+ if _item_top_countries is not None
+ else None
+ )
_dict["topCountries"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in top_labels (list)
_items = []
if self.top_labels:
for _item_top_labels in self.top_labels:
- if _item_top_labels:
- _items.append(_item_top_labels.to_dict())
+ _items.append(
+ _item_top_labels.to_dict() if _item_top_labels is not None else None
+ )
_dict["topLabels"] = _items
# override the default output from pydantic by calling `to_dict()` of logo_large
if self.logo_large:
@@ -327,58 +335,46 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"description": obj.get("description"),
"avgMonthlyPosts": obj.get("avgMonthlyPosts"),
"paywall": obj.get("paywall"),
- "location": (
- SourceLocation.from_dict(obj["location"])
- if obj.get("location") is not None
- else None
- ),
- "topCategories": (
- [
- SourceTopStatHolder.from_dict(_item)
- for _item in obj["topCategories"]
- ]
- if obj.get("topCategories") is not None
- else None
- ),
- "topTopics": (
- [SourceTopStatHolder.from_dict(_item) for _item in obj["topTopics"]]
- if obj.get("topTopics") is not None
- else None
- ),
- "topCountries": (
- [
- SourceTopStatHolder.from_dict(_item)
- for _item in obj["topCountries"]
- ]
- if obj.get("topCountries") is not None
- else None
- ),
- "topLabels": (
- [SourceTopStatHolder.from_dict(_item) for _item in obj["topLabels"]]
- if obj.get("topLabels") is not None
- else None
- ),
+ "location": SourceLocation.from_dict(obj["location"])
+ if obj.get("location") is not None
+ else None,
+ "topCategories": [
+ SourceTopStatHolder.from_dict(_item)
+ for _item in obj["topCategories"]
+ ]
+ if obj.get("topCategories") is not None
+ else None,
+ "topTopics": [
+ SourceTopStatHolder.from_dict(_item) for _item in obj["topTopics"]
+ ]
+ if obj.get("topTopics") is not None
+ else None,
+ "topCountries": [
+ SourceTopStatHolder.from_dict(_item)
+ for _item in obj["topCountries"]
+ ]
+ if obj.get("topCountries") is not None
+ else None,
+ "topLabels": [
+ SourceTopStatHolder.from_dict(_item) for _item in obj["topLabels"]
+ ]
+ if obj.get("topLabels") is not None
+ else None,
"avgBiasRating": obj.get("avgBiasRating"),
"adFontesBiasRating": obj.get("adFontesBiasRating"),
"allSidesBiasRating": obj.get("allSidesBiasRating"),
"mbfcBiasRating": obj.get("mbfcBiasRating"),
"monthlyVisits": obj.get("monthlyVisits"),
"globalRank": obj.get("globalRank"),
- "logoLarge": (
- ImageHolder.from_dict(obj["logoLarge"])
- if obj.get("logoLarge") is not None
- else None
- ),
- "logoFavIcon": (
- ImageHolder.from_dict(obj["logoFavIcon"])
- if obj.get("logoFavIcon") is not None
- else None
- ),
- "logoSquare": (
- ImageHolder.from_dict(obj["logoSquare"])
- if obj.get("logoSquare") is not None
- else None
- ),
+ "logoLarge": ImageHolder.from_dict(obj["logoLarge"])
+ if obj.get("logoLarge") is not None
+ else None,
+ "logoFavIcon": ImageHolder.from_dict(obj["logoFavIcon"])
+ if obj.get("logoFavIcon") is not None
+ else None,
+ "logoSquare": ImageHolder.from_dict(obj["logoSquare"])
+ if obj.get("logoSquare") is not None
+ else None,
}
)
return _obj
diff --git a/src/perigon/models/source_group_dto.py b/src/perigon/models/source_group_dto.py
new file mode 100644
index 0000000..04f151c
--- /dev/null
+++ b/src/perigon/models/source_group_dto.py
@@ -0,0 +1,184 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class SourceGroupDto(BaseModel):
+ """
+ Named group of news source domains for filtering search and monitors.
+ """
+
+ id: Optional[StrictInt] = Field(
+ default=None, description="Numeric identifier for the source group."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the source group was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the source group was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Unique slug name for the source group."
+ )
+ display_name: Optional[StrictStr] = Field(
+ default=None, description="Human-readable display name.", alias="displayName"
+ )
+ description: Optional[StrictStr] = Field(
+ default=None, description="Optional description of the source group."
+ )
+ domains: Optional[List[StrictStr]] = Field(
+ default=None, description="Publisher domains included in the group."
+ )
+ organization_id: Optional[StrictInt] = Field(
+ default=None,
+ description="Owning organization ID when the source group is private.",
+ alias="organizationId",
+ )
+ visible: Optional[StrictBool] = Field(
+ default=None, description="Whether the source group is publicly visible."
+ )
+ __properties: ClassVar[List[str]] = [
+ "id",
+ "createdAt",
+ "updatedAt",
+ "name",
+ "displayName",
+ "description",
+ "domains",
+ "organizationId",
+ "visible",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SourceGroupDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if id (nullable) is None
+ # and model_fields_set contains the field
+ if self.id is None and "id" in self.model_fields_set:
+ _dict["id"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if display_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.display_name is None and "display_name" in self.model_fields_set:
+ _dict["displayName"] = None
+
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if domains (nullable) is None
+ # and model_fields_set contains the field
+ if self.domains is None and "domains" in self.model_fields_set:
+ _dict["domains"] = None
+
+ # set to None if organization_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.organization_id is None and "organization_id" in self.model_fields_set:
+ _dict["organizationId"] = None
+
+ # set to None if visible (nullable) is None
+ # and model_fields_set contains the field
+ if self.visible is None and "visible" in self.model_fields_set:
+ _dict["visible"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SourceGroupDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "id": obj.get("id"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "name": obj.get("name"),
+ "displayName": obj.get("displayName"),
+ "description": obj.get("description"),
+ "domains": obj.get("domains"),
+ "organizationId": obj.get("organizationId"),
+ "visible": obj.get("visible"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/source_group_list_result.py b/src/perigon/models/source_group_list_result.py
new file mode 100644
index 0000000..271887d
--- /dev/null
+++ b/src/perigon/models/source_group_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.source_group_dto import SourceGroupDto
+
+
+class SourceGroupListResult(BaseModel):
+ """
+ Paginated list of source groups.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching source groups across all pages."
+ )
+ data: List[SourceGroupDto] = Field(
+ description="Source groups returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SourceGroupListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SourceGroupListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [SourceGroupDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/perigon/models/vector_search_result.py b/src/perigon/models/source_group_resolve_result.py
similarity index 65%
rename from perigon/models/vector_search_result.py
rename to src/perigon/models/source_group_resolve_result.py
index c7d0093..f28ace6 100644
--- a/perigon/models/vector_search_result.py
+++ b/src/perigon/models/source_group_resolve_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,23 +19,24 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt
+from pydantic import BaseModel, ConfigDict, Field
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
-from perigon.models.scored_article import ScoredArticle
+from perigon.models.source_group_dto import SourceGroupDto
-class VectorSearchResult(BaseModel):
+class SourceGroupResolveResult(BaseModel):
+ """
+ Resolved source groups response.
"""
- Vector search result
- """ # noqa: E501
- status: StrictInt
- results: List[ScoredArticle]
- __properties: ClassVar[List[str]] = ["status", "results"]
+ data: List[SourceGroupDto] = Field(description="Resolved source groups.")
+ __properties: ClassVar[List[str]] = ["data"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,12 +47,11 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
- """Create an instance of VectorSearchResult from a JSON string"""
+ """Create an instance of SourceGroupResolveResult from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
@@ -72,18 +71,17 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
- # override the default output from pydantic by calling `to_dict()` of each item in results (list)
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
_items = []
- if self.results:
- for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
- _dict["results"] = _items
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
- """Create an instance of VectorSearchResult from a dict"""
+ """Create an instance of SourceGroupResolveResult from a dict"""
if obj is None:
return None
@@ -92,12 +90,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "status": obj.get("status"),
- "results": (
- [ScoredArticle.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "data": [SourceGroupDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None
}
)
return _obj
diff --git a/src/perigon/models/source_group_result.py b/src/perigon/models/source_group_result.py
new file mode 100644
index 0000000..9fcf813
--- /dev/null
+++ b/src/perigon/models/source_group_result.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.source_group_dto import SourceGroupDto
+
+
+class SourceGroupResult(BaseModel):
+ """
+ Single source group response.
+ """
+
+ data: SourceGroupDto
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of SourceGroupResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data
+ if self.data:
+ _dict["data"] = self.data.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of SourceGroupResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": SourceGroupDto.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/source_group_sort_by.py b/src/perigon/models/source_group_sort_by.py
new file mode 100644
index 0000000..9f3e716
--- /dev/null
+++ b/src/perigon/models/source_group_sort_by.py
@@ -0,0 +1,39 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+from enum import Enum
+
+from typing_extensions import Self
+
+
+class SourceGroupSortBy(str, Enum):
+ """
+ SourceGroupSortBy
+ """
+
+ """
+ allowed enum values
+ """
+ ID = "id"
+ CREATEDAT = "createdAt"
+ UPDATEDAT = "updatedAt"
+ NAME = "name"
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of SourceGroupSortBy from a JSON string"""
+ return cls(json.loads(json_str))
diff --git a/perigon/models/source_holder.py b/src/perigon/models/source_holder.py
similarity index 83%
rename from perigon/models/source_holder.py
rename to src/perigon/models/source_holder.py
index 351ec06..128e634 100644
--- a/perigon/models/source_holder.py
+++ b/src/perigon/models/source_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,7 +19,8 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.source_location import SourceLocation
@@ -28,16 +28,19 @@
class SourceHolder(BaseModel):
"""
- SourceHolder
- """ # noqa: E501
+ Publisher information for the selected story image.
+ """
- domain: Optional[StrictStr] = None
- paywall: Optional[StrictBool] = None
+ domain: Optional[StrictStr] = Field(default=None, description="Publisher domain.")
+ paywall: Optional[StrictBool] = Field(
+ default=None, description="Whether the publisher uses a paywall, when known."
+ )
location: Optional[SourceLocation] = None
__properties: ClassVar[List[str]] = ["domain", "paywall", "location"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +51,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -106,11 +108,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"domain": obj.get("domain"),
"paywall": obj.get("paywall"),
- "location": (
- SourceLocation.from_dict(obj["location"])
- if obj.get("location") is not None
- else None
- ),
+ "location": SourceLocation.from_dict(obj["location"])
+ if obj.get("location") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/source_location.py b/src/perigon/models/source_location.py
similarity index 80%
rename from perigon/models/source_location.py
rename to src/perigon/models/source_location.py
index fab6d85..20e47ab 100644
--- a/perigon/models/source_location.py
+++ b/src/perigon/models/source_location.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,7 +19,8 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.coordinate import Coordinate
@@ -28,13 +28,21 @@
class SourceLocation(BaseModel):
"""
- SourceLocation
- """ # noqa: E501
+ Geographic location of the publisher, when available.
+ """
- country: Optional[StrictStr] = None
- state: Optional[StrictStr] = None
- county: Optional[StrictStr] = None
- city: Optional[StrictStr] = None
+ country: Optional[StrictStr] = Field(
+ default=None, description="Country where the publisher is located."
+ )
+ state: Optional[StrictStr] = Field(
+ default=None, description="State or region where the publisher is located."
+ )
+ county: Optional[StrictStr] = Field(
+ default=None, description="County where the publisher is located."
+ )
+ city: Optional[StrictStr] = Field(
+ default=None, description="City where the publisher is located."
+ )
coordinates: Optional[Coordinate] = None
__properties: ClassVar[List[str]] = [
"country",
@@ -45,7 +53,8 @@ class SourceLocation(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -56,8 +65,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -126,11 +134,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"state": obj.get("state"),
"county": obj.get("county"),
"city": obj.get("city"),
- "coordinates": (
- Coordinate.from_dict(obj["coordinates"])
- if obj.get("coordinates") is not None
- else None
- ),
+ "coordinates": Coordinate.from_dict(obj["coordinates"])
+ if obj.get("coordinates") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/source_search_result.py b/src/perigon/models/source_search_result.py
similarity index 85%
rename from perigon/models/source_search_result.py
rename to src/perigon/models/source_search_result.py
index 0c94bcf..fdb21b4 100644
--- a/perigon/models/source_search_result.py
+++ b/src/perigon/models/source_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.source import Source
@@ -29,7 +29,7 @@
class SourceSearchResult(BaseModel):
"""
Source search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class SourceSearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [Source.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [Source.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/source_top_stat_holder.py b/src/perigon/models/source_top_stat_holder.py
similarity index 93%
rename from perigon/models/source_top_stat_holder.py
rename to src/perigon/models/source_top_stat_holder.py
index ffc8a37..58f9d8a 100644
--- a/perigon/models/source_top_stat_holder.py
+++ b/src/perigon/models/source_top_stat_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,20 +20,22 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class SourceTopStatHolder(BaseModel):
"""
SourceTopStatHolder
- """ # noqa: E501
+ """
name: Optional[StrictStr] = None
count: Optional[StrictInt] = None
__properties: ClassVar[List[str]] = ["name", "count"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +46,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/src/perigon/models/sources_sort_by.py b/src/perigon/models/sources_sort_by.py
new file mode 100644
index 0000000..98dba9b
--- /dev/null
+++ b/src/perigon/models/sources_sort_by.py
@@ -0,0 +1,39 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+from enum import Enum
+
+from typing_extensions import Self
+
+
+class SourcesSortBy(str, Enum):
+ """
+ SourcesSortBy
+ """
+
+ """
+ allowed enum values
+ """
+ GLOBALRANK = "globalRank"
+ RELEVANCE = "relevance"
+ MONTHLYVISITS = "monthlyVisits"
+ AVGMONTHLYPOSTS = "avgMonthlyPosts"
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of SourcesSortBy from a JSON string"""
+ return cls(json.loads(json_str))
diff --git a/perigon/models/stat_result.py b/src/perigon/models/stat_result.py
similarity index 69%
rename from perigon/models/stat_result.py
rename to src/perigon/models/stat_result.py
index 346484d..48d7f7e 100644
--- a/perigon/models/stat_result.py
+++ b/src/perigon/models/stat_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,21 +19,32 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictInt
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class StatResult(BaseModel):
"""
StatResult
- """ # noqa: E501
+ """
- status: Optional[StrictInt] = None
- results: Optional[List[Dict[str, Any]]] = None
- __properties: ClassVar[List[str]] = ["status", "results"]
+ status: Optional[StrictInt] = Field(
+ default=None, description="HTTP status code for the response."
+ )
+ results: Optional[List[Dict[str, Any]]] = Field(
+ default=None, description="Statistics returned by the request."
+ )
+ num_results: Optional[StrictInt] = Field(
+ default=None,
+ description="Total number of matching records, when calculated by the endpoint.",
+ alias="numResults",
+ )
+ __properties: ClassVar[List[str]] = ["status", "results", "numResults"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +55,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -80,6 +89,11 @@ def to_dict(self) -> Dict[str, Any]:
if self.results is None and "results" in self.model_fields_set:
_dict["results"] = None
+ # set to None if num_results (nullable) is None
+ # and model_fields_set contains the field
+ if self.num_results is None and "num_results" in self.model_fields_set:
+ _dict["numResults"] = None
+
return _dict
@classmethod
@@ -92,6 +106,10 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate(
- {"status": obj.get("status"), "results": obj.get("results")}
+ {
+ "status": obj.get("status"),
+ "results": obj.get("results"),
+ "numResults": obj.get("numResults"),
+ }
)
return _obj
diff --git a/perigon/models/story_history_record.py b/src/perigon/models/story_history_record.py
similarity index 69%
rename from perigon/models/story_history_record.py
rename to src/perigon/models/story_history_record.py
index 3166710..9fe31bd 100644
--- a/perigon/models/story_history_record.py
+++ b/src/perigon/models/story_history_record.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.key_point import KeyPoint
@@ -29,18 +29,49 @@
class StoryHistoryRecord(BaseModel):
"""
- StoryHistoryRecord
- """ # noqa: E501
-
- cluster_id: Optional[StrictStr] = Field(default=None, alias="clusterId")
- created_at: Optional[StrictStr] = Field(default=None, alias="createdAt")
- name: Optional[StrictStr] = None
- triggered_at: Optional[StrictStr] = Field(default=None, alias="triggeredAt")
- summary: Optional[StrictStr] = None
- short_summary: Optional[StrictStr] = Field(default=None, alias="shortSummary")
- changelog: Optional[StrictStr] = None
- key_points: Optional[List[KeyPoint]] = Field(default=None, alias="keyPoints")
- questions: Optional[List[Question]] = None
+ History records returned for the requested page.
+ """
+
+ cluster_id: Optional[StrictStr] = Field(
+ default=None,
+ description="Unique identifier of the story associated with this history record.",
+ alias="clusterId",
+ )
+ created_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the history record was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ name: Optional[StrictStr] = Field(
+ default=None,
+ description="Story name captured in this history record, when available.",
+ )
+ triggered_at: Optional[StrictStr] = Field(
+ default=None,
+ description="Date and time the story refresh that produced this record was triggered, in ISO 8601 format.",
+ alias="triggeredAt",
+ )
+ summary: Optional[StrictStr] = Field(
+ default=None, description="Story summary captured in this history record."
+ )
+ short_summary: Optional[StrictStr] = Field(
+ default=None,
+ description="Short story summary captured in this history record, when available.",
+ alias="shortSummary",
+ )
+ changelog: Optional[StrictStr] = Field(
+ default=None,
+ description="Description of changes from the previous story version, when available.",
+ )
+ key_points: Optional[List[KeyPoint]] = Field(
+ default=None,
+ description="Key points captured for this version of the story.",
+ alias="keyPoints",
+ )
+ questions: Optional[List[Question]] = Field(
+ default=None,
+ description="Questions and answers generated for this version of the story.",
+ )
__properties: ClassVar[List[str]] = [
"clusterId",
"createdAt",
@@ -54,7 +85,8 @@ class StoryHistoryRecord(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -65,8 +97,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -94,15 +125,17 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.key_points:
for _item_key_points in self.key_points:
- if _item_key_points:
- _items.append(_item_key_points.to_dict())
+ _items.append(
+ _item_key_points.to_dict() if _item_key_points is not None else None
+ )
_dict["keyPoints"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in questions (list)
_items = []
if self.questions:
for _item_questions in self.questions:
- if _item_questions:
- _items.append(_item_questions.to_dict())
+ _items.append(
+ _item_questions.to_dict() if _item_questions is not None else None
+ )
_dict["questions"] = _items
# set to None if cluster_id (nullable) is None
# and model_fields_set contains the field
@@ -169,16 +202,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"summary": obj.get("summary"),
"shortSummary": obj.get("shortSummary"),
"changelog": obj.get("changelog"),
- "keyPoints": (
- [KeyPoint.from_dict(_item) for _item in obj["keyPoints"]]
- if obj.get("keyPoints") is not None
- else None
- ),
- "questions": (
- [Question.from_dict(_item) for _item in obj["questions"]]
- if obj.get("questions") is not None
- else None
- ),
+ "keyPoints": [KeyPoint.from_dict(_item) for _item in obj["keyPoints"]]
+ if obj.get("keyPoints") is not None
+ else None,
+ "questions": [Question.from_dict(_item) for _item in obj["questions"]]
+ if obj.get("questions") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/story_history_result.py b/src/perigon/models/story_history_result.py
similarity index 77%
rename from perigon/models/story_history_result.py
rename to src/perigon/models/story_history_result.py
index 853e3e7..7802846 100644
--- a/perigon/models/story_history_result.py
+++ b/src/perigon/models/story_history_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.story_history_record import StoryHistoryRecord
@@ -29,15 +29,20 @@
class StoryHistoryResult(BaseModel):
"""
Story history result
- """ # noqa: E501
+ """
- status: StrictInt
- num_results: StrictInt = Field(alias="numResults")
- results: List[StoryHistoryRecord]
+ status: StrictInt = Field(description="HTTP status code for the response.")
+ num_results: StrictInt = Field(
+ description="Total number of matching history records.", alias="numResults"
+ )
+ results: List[StoryHistoryRecord] = Field(
+ description="History records returned for the requested page."
+ )
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +53,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +81,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +100,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [StoryHistoryRecord.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [
+ StoryHistoryRecord.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/story_search_result.py b/src/perigon/models/story_search_result.py
similarity index 85%
rename from perigon/models/story_search_result.py
rename to src/perigon/models/story_search_result.py
index 20df54a..751a6e9 100644
--- a/perigon/models/story_search_result.py
+++ b/src/perigon/models/story_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.news_cluster import NewsCluster
@@ -29,7 +29,7 @@
class StorySearchResult(BaseModel):
"""
Story search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class StorySearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [NewsCluster.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [NewsCluster.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/summary_body.py b/src/perigon/models/summary_body.py
similarity index 72%
rename from perigon/models/summary_body.py
rename to src/perigon/models/summary_body.py
index 1e1b967..1539695 100644
--- a/perigon/models/summary_body.py
+++ b/src/perigon/models/summary_body.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,22 +20,23 @@
from typing import Any, ClassVar, Dict, List, Optional, Set, Union
from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
class SummaryBody(BaseModel):
"""
Request body for the Search-Summarizer endpoint. Contains the userβs summary instructions, how many articles to include, which fields to use, the retrieval method, and LLM generation settings.
- """ # noqa: E501
+ """
prompt: Optional[StrictStr] = Field(
default="You are a helpful assistant tasked with summarizing the search results. In a casual, natural tone, craft a vivid, insightful synthesis in one fluid paragraph (no more than 125 words). Cover as many relevant points as possible, connect ideas, highlight themes, and surface anything interesting or surprising.",
- description="Instructions guiding how the summary should be written. Maximum length: 2 048 characters.",
+ description="Instructions guiding how the summary should be written. Maximum length: 2,048 characters.",
)
max_article_count: Optional[Annotated[int, Field(le=100, strict=True, ge=1)]] = (
Field(
default=10,
- description="The maximum number of articles to factor into the summary.",
+ description="Maximum number of articles used to generate the summary. Must be between 1 and 100.",
alias="maxArticleCount",
)
)
@@ -44,7 +44,7 @@ class SummaryBody(BaseModel):
Annotated[int, Field(le=100, strict=True, ge=1)]
] = Field(
default=10,
- description="The maximum number of articles that should be returned in the response. This can be used to return fewer than maxArticleCount results.",
+ description="Maximum number of articles returned in the response. Must be between 1 and 100 and cannot return more articles than maxArticleCount selected.",
alias="returnedArticleCount",
)
summarize_fields: Optional[List[StrictStr]] = Field(
@@ -54,10 +54,11 @@ class SummaryBody(BaseModel):
)
method: Optional[StrictStr] = Field(
default="ARTICLES",
- description="Method for selecting articles: ARTICLES (include all matches) or CLUSTERS (one per cluster).",
+ description="Article selection method. ARTICLES selects up to maxArticleCount matching articles; CLUSTERS selects the first article from each of up to maxArticleCount stories.",
)
model: Optional[StrictStr] = Field(
- default="gpt-4.1", description="The underlying LLM model to use for generation."
+ default="gpt-6-luna",
+ description="The underlying LLM model to use for generation.",
)
temperature: Optional[
Union[
@@ -66,7 +67,7 @@ class SummaryBody(BaseModel):
]
] = Field(
default=0.7,
- description="Sampling temperature for the LLM (0.0 = deterministic to 2.0 = very creative).",
+ description="Sampling temperature from 0.0 to 2.0. Ignored by gpt-6-luna and gpt-6.1-sol.",
)
top_p: Optional[
Union[
@@ -75,12 +76,12 @@ class SummaryBody(BaseModel):
]
] = Field(
default=1.0,
- description="Nucleus sampling (top-p) for the LLM (0.0 to 1.0).",
+ description="Nucleus sampling (top-p) for the LLM (0.0 to 1.0). Ignored by gpt-6-luna and gpt-6.1-sol.",
alias="topP",
)
max_tokens: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(
default=2048,
- description="Maximum number of tokens to generate in the summary.",
+ description="Maximum number of tokens to generate. Must be 0 or greater.",
alias="maxTokens",
)
__properties: ClassVar[List[str]] = [
@@ -124,27 +125,15 @@ def model_validate_enum(cls, value):
if value is None:
return value
- if value not in set(
- [
- "gpt-5.4-mini",
- "gpt-5.4-nano",
- "gpt-5-mini",
- "gpt-5-nano",
- "gpt-4o",
- "gpt-4o-mini",
- "gpt-4.1",
- "gpt-4.1-mini",
- "llama-3.3-70b-versatile",
- "openai/gpt-oss-120b",
- ]
- ):
+ if value not in set(["gpt-6-luna", "gpt-6.1-sol", "openai/gpt-oss-120b"]):
raise ValueError(
- "must be one of enum values ('gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-mini', 'llama-3.3-70b-versatile', 'openai/gpt-oss-120b')"
+ "must be one of enum values ('gpt-6-luna', 'gpt-6.1-sol', 'openai/gpt-oss-120b')"
)
return value
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -155,8 +144,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -247,37 +235,21 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
- "prompt": (
- obj.get("prompt")
- if obj.get("prompt") is not None
- else "You are a helpful assistant tasked with summarizing the search results. In a casual, natural tone, craft a vivid, insightful synthesis in one fluid paragraph (no more than 125 words). Cover as many relevant points as possible, connect ideas, highlight themes, and surface anything interesting or surprising."
- ),
- "maxArticleCount": (
- obj.get("maxArticleCount")
- if obj.get("maxArticleCount") is not None
- else 10
- ),
- "returnedArticleCount": (
- obj.get("returnedArticleCount")
- if obj.get("returnedArticleCount") is not None
- else 10
- ),
+ "prompt": obj.get("prompt")
+ if "prompt" in obj
+ else "You are a helpful assistant tasked with summarizing the search results. In a casual, natural tone, craft a vivid, insightful synthesis in one fluid paragraph (no more than 125 words). Cover as many relevant points as possible, connect ideas, highlight themes, and surface anything interesting or surprising.",
+ "maxArticleCount": obj.get("maxArticleCount")
+ if "maxArticleCount" in obj
+ else 10,
+ "returnedArticleCount": obj.get("returnedArticleCount")
+ if "returnedArticleCount" in obj
+ else 10,
"summarizeFields": obj.get("summarizeFields"),
- "method": (
- obj.get("method") if obj.get("method") is not None else "ARTICLES"
- ),
- "model": (
- obj.get("model") if obj.get("model") is not None else "gpt-4.1"
- ),
- "temperature": (
- obj.get("temperature")
- if obj.get("temperature") is not None
- else 0.7
- ),
- "topP": obj.get("topP") if obj.get("topP") is not None else 1.0,
- "maxTokens": (
- obj.get("maxTokens") if obj.get("maxTokens") is not None else 2048
- ),
+ "method": obj.get("method") if "method" in obj else "ARTICLES",
+ "model": obj.get("model") if "model" in obj else "gpt-6-luna",
+ "temperature": obj.get("temperature") if "temperature" in obj else 0.7,
+ "topP": obj.get("topP") if "topP" in obj else 1.0,
+ "maxTokens": obj.get("maxTokens") if "maxTokens" in obj else 2048,
}
)
return _obj
diff --git a/perigon/models/summary_search_result.py b/src/perigon/models/summary_search_result.py
similarity index 74%
rename from perigon/models/summary_search_result.py
rename to src/perigon/models/summary_search_result.py
index 0376306..2f8eacf 100644
--- a/perigon/models/summary_search_result.py
+++ b/src/perigon/models/summary_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.article import Article
@@ -29,16 +29,24 @@
class SummarySearchResult(BaseModel):
"""
SummarySearchResult
- """ # noqa: E501
+ """
- status: StrictInt
- num_results: StrictInt = Field(alias="numResults")
- summary: StrictStr
- results: List[Article]
+ status: StrictInt = Field(description="HTTP status code for the response.")
+ num_results: StrictInt = Field(
+ description="Number of articles returned in results after applying returnedArticleCount.",
+ alias="numResults",
+ )
+ summary: StrictStr = Field(
+ description="Generated summary of the selected articles, or 'No articles found' when no content matches."
+ )
+ results: List[Article] = Field(
+ description="Articles returned after summary generation, limited by returnedArticleCount."
+ )
__properties: ClassVar[List[str]] = ["status", "numResults", "summary", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -49,8 +57,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -78,8 +85,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -97,11 +105,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"status": obj.get("status"),
"numResults": obj.get("numResults"),
"summary": obj.get("summary"),
- "results": (
- [Article.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [Article.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/symbol_holder.py b/src/perigon/models/symbol_holder.py
similarity index 83%
rename from perigon/models/symbol_holder.py
rename to src/perigon/models/symbol_holder.py
index 721acef..87dff66 100644
--- a/perigon/models/symbol_holder.py
+++ b/src/perigon/models/symbol_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class SymbolHolder(BaseModel):
"""
SymbolHolder
- """ # noqa: E501
+ """
symbol: Optional[StrictStr] = None
exchange: Optional[StrictStr] = None
@@ -35,15 +35,20 @@ class SymbolHolder(BaseModel):
default=None, alias="exchangeShortName"
)
ipo_date: Optional[StrictStr] = Field(default=None, alias="ipoDate")
+ isin: Optional[StrictStr] = None
+ cusip: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = [
"symbol",
"exchange",
"exchangeShortName",
"ipoDate",
+ "isin",
+ "cusip",
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,8 +59,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -102,6 +106,16 @@ def to_dict(self) -> Dict[str, Any]:
if self.ipo_date is None and "ipo_date" in self.model_fields_set:
_dict["ipoDate"] = None
+ # set to None if isin (nullable) is None
+ # and model_fields_set contains the field
+ if self.isin is None and "isin" in self.model_fields_set:
+ _dict["isin"] = None
+
+ # set to None if cusip (nullable) is None
+ # and model_fields_set contains the field
+ if self.cusip is None and "cusip" in self.model_fields_set:
+ _dict["cusip"] = None
+
return _dict
@classmethod
@@ -119,6 +133,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"exchange": obj.get("exchange"),
"exchangeShortName": obj.get("exchangeShortName"),
"ipoDate": obj.get("ipoDate"),
+ "isin": obj.get("isin"),
+ "cusip": obj.get("cusip"),
}
)
return _obj
diff --git a/perigon/models/topic_dto.py b/src/perigon/models/topic_dto.py
similarity index 91%
rename from perigon/models/topic_dto.py
rename to src/perigon/models/topic_dto.py
index dec9166..1c1313c 100644
--- a/perigon/models/topic_dto.py
+++ b/src/perigon/models/topic_dto.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -22,6 +21,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.topic_labels import TopicLabels
@@ -30,7 +30,7 @@
class TopicDto(BaseModel):
"""
TopicDto
- """ # noqa: E501
+ """
id: Optional[StrictInt] = None
created_at: Optional[datetime] = Field(default=None, alias="createdAt")
@@ -46,7 +46,8 @@ class TopicDto(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -57,8 +58,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -127,11 +127,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"createdAt": obj.get("createdAt"),
"updatedAt": obj.get("updatedAt"),
"name": obj.get("name"),
- "labels": (
- TopicLabels.from_dict(obj["labels"])
- if obj.get("labels") is not None
- else None
- ),
+ "labels": TopicLabels.from_dict(obj["labels"])
+ if obj.get("labels") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/topic_holder.py b/src/perigon/models/topic_holder.py
similarity index 86%
rename from perigon/models/topic_holder.py
rename to src/perigon/models/topic_holder.py
index ffc4c72..ecd9d03 100644
--- a/perigon/models/topic_holder.py
+++ b/src/perigon/models/topic_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -20,20 +19,24 @@
import re # noqa: F401
from typing import Any, ClassVar, Dict, List, Optional, Set
-from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class TopicHolder(BaseModel):
"""
- TopicHolder
- """ # noqa: E501
+ Topics most strongly associated with the story.
+ """
- name: Optional[StrictStr] = None
+ name: Optional[StrictStr] = Field(
+ default=None, description="Topic assigned to the article."
+ )
__properties: ClassVar[List[str]] = ["name"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -44,8 +47,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/topic_labels.py b/src/perigon/models/topic_labels.py
similarity index 93%
rename from perigon/models/topic_labels.py
rename to src/perigon/models/topic_labels.py
index 84110af..0f5e0c5 100644
--- a/perigon/models/topic_labels.py
+++ b/src/perigon/models/topic_labels.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,20 +20,22 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class TopicLabels(BaseModel):
"""
TopicLabels
- """ # noqa: E501
+ """
category: Optional[StrictStr] = None
subcategory: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = ["category", "subcategory"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +46,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/topic_search_result.py b/src/perigon/models/topic_search_result.py
similarity index 84%
rename from perigon/models/topic_search_result.py
rename to src/perigon/models/topic_search_result.py
index 7614e16..7a9b392 100644
--- a/perigon/models/topic_search_result.py
+++ b/src/perigon/models/topic_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.topic_dto import TopicDto
@@ -29,14 +29,15 @@
class TopicSearchResult(BaseModel):
"""
Topic search result
- """ # noqa: E501
+ """
total: StrictInt
data: List[TopicDto]
__properties: ClassVar[List[str]] = ["total", "data"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,8 +48,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -76,8 +76,7 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.data:
for _item_data in self.data:
- if _item_data:
- _items.append(_item_data.to_dict())
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
_dict["data"] = _items
return _dict
@@ -93,11 +92,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"total": obj.get("total"),
- "data": (
- [TopicDto.from_dict(_item) for _item in obj["data"]]
- if obj.get("data") is not None
- else None
- ),
+ "data": [TopicDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/update_watchlist_params.py b/src/perigon/models/update_watchlist_params.py
similarity index 86%
rename from perigon/models/update_watchlist_params.py
rename to src/perigon/models/update_watchlist_params.py
index e8e94f2..99e8d38 100644
--- a/perigon/models/update_watchlist_params.py
+++ b/src/perigon/models/update_watchlist_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.watchlist_company import WatchlistCompany
@@ -30,7 +30,7 @@
class UpdateWatchlistParams(BaseModel):
"""
UpdateWatchlistParams
- """ # noqa: E501
+ """
name: Optional[StrictStr] = Field(
default=None, description="New name for the watchlist"
@@ -63,7 +63,8 @@ class UpdateWatchlistParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -74,8 +75,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -103,15 +103,17 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.people:
for _item_people in self.people:
- if _item_people:
- _items.append(_item_people.to_dict())
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
_dict["people"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in companies (list)
_items = []
if self.companies:
for _item_companies in self.companies:
- if _item_companies:
- _items.append(_item_companies.to_dict())
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
_dict["companies"] = _items
# set to None if name (nullable) is None
# and model_fields_set contains the field
@@ -159,16 +161,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"name": obj.get("name"),
"displayName": obj.get("displayName"),
"description": obj.get("description"),
- "people": (
- [WatchlistPerson.from_dict(_item) for _item in obj["people"]]
- if obj.get("people") is not None
- else None
- ),
- "companies": (
- [WatchlistCompany.from_dict(_item) for _item in obj["companies"]]
- if obj.get("companies") is not None
- else None
- ),
+ "people": [WatchlistPerson.from_dict(_item) for _item in obj["people"]]
+ if obj.get("people") is not None
+ else None,
+ "companies": [
+ WatchlistCompany.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
"visible": obj.get("visible"),
}
)
diff --git a/perigon/models/watchlist_company.py b/src/perigon/models/watchlist_company.py
similarity index 89%
rename from perigon/models/watchlist_company.py
rename to src/perigon/models/watchlist_company.py
index 1813a95..c19371a 100644
--- a/perigon/models/watchlist_company.py
+++ b/src/perigon/models/watchlist_company.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,29 +20,33 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WatchlistCompany(BaseModel):
"""
Updated list of companies to include in the watchlist
- """ # noqa: E501
+ """
name: Optional[StrictStr] = None
description: Optional[StrictStr] = None
id: Optional[StrictStr] = None
domain: Optional[StrictStr] = None
aliases: Optional[List[StrictStr]] = None
+ query: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = [
"name",
"description",
"id",
"domain",
"aliases",
+ "query",
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -54,8 +57,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -104,6 +106,11 @@ def to_dict(self) -> Dict[str, Any]:
if self.aliases is None and "aliases" in self.model_fields_set:
_dict["aliases"] = None
+ # set to None if query (nullable) is None
+ # and model_fields_set contains the field
+ if self.query is None and "query" in self.model_fields_set:
+ _dict["query"] = None
+
return _dict
@classmethod
@@ -122,6 +129,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"id": obj.get("id"),
"domain": obj.get("domain"),
"aliases": obj.get("aliases"),
+ "query": obj.get("query"),
}
)
return _obj
diff --git a/src/perigon/models/watchlist_company_dto.py b/src/perigon/models/watchlist_company_dto.py
new file mode 100644
index 0000000..eeab430
--- /dev/null
+++ b/src/perigon/models/watchlist_company_dto.py
@@ -0,0 +1,135 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class WatchlistCompanyDto(BaseModel):
+ """
+ Companies included in the watchlist.
+ """
+
+ name: Optional[StrictStr] = None
+ description: Optional[StrictStr] = None
+ id: Optional[StrictStr] = None
+ domain: Optional[StrictStr] = None
+ aliases: Optional[List[StrictStr]] = None
+ query: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "description",
+ "id",
+ "domain",
+ "aliases",
+ "query",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistCompanyDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if id (nullable) is None
+ # and model_fields_set contains the field
+ if self.id is None and "id" in self.model_fields_set:
+ _dict["id"] = None
+
+ # set to None if domain (nullable) is None
+ # and model_fields_set contains the field
+ if self.domain is None and "domain" in self.model_fields_set:
+ _dict["domain"] = None
+
+ # set to None if aliases (nullable) is None
+ # and model_fields_set contains the field
+ if self.aliases is None and "aliases" in self.model_fields_set:
+ _dict["aliases"] = None
+
+ # set to None if query (nullable) is None
+ # and model_fields_set contains the field
+ if self.query is None and "query" in self.model_fields_set:
+ _dict["query"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistCompanyDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "description": obj.get("description"),
+ "id": obj.get("id"),
+ "domain": obj.get("domain"),
+ "aliases": obj.get("aliases"),
+ "query": obj.get("query"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/watchlist_dto.py b/src/perigon/models/watchlist_dto.py
new file mode 100644
index 0000000..8e8c6e3
--- /dev/null
+++ b/src/perigon/models/watchlist_dto.py
@@ -0,0 +1,221 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.watchlist_company_dto import WatchlistCompanyDto
+from perigon.models.watchlist_person_dto import WatchlistPersonDto
+
+
+class WatchlistDto(BaseModel):
+ """
+ Watchlist of people and companies for use with monitors.
+ """
+
+ id: Optional[StrictInt] = Field(
+ default=None, description="Numeric identifier for the watchlist."
+ )
+ created_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the watchlist was created, in ISO 8601 format.",
+ alias="createdAt",
+ )
+ updated_at: Optional[datetime] = Field(
+ default=None,
+ description="Date and time the watchlist was last updated, in ISO 8601 format.",
+ alias="updatedAt",
+ )
+ name: Optional[StrictStr] = Field(
+ default=None, description="Unique slug name for the watchlist."
+ )
+ display_name: Optional[StrictStr] = Field(
+ default=None, description="Human-readable display name.", alias="displayName"
+ )
+ description: Optional[StrictStr] = Field(
+ default=None, description="Optional description of the watchlist."
+ )
+ people: Optional[List[WatchlistPersonDto]] = Field(
+ default=None, description="People included in the watchlist."
+ )
+ companies: Optional[List[WatchlistCompanyDto]] = Field(
+ default=None, description="Companies included in the watchlist."
+ )
+ organization_id: Optional[StrictInt] = Field(
+ default=None,
+ description="Owning organization ID when the watchlist is private.",
+ alias="organizationId",
+ )
+ visible: Optional[StrictBool] = Field(
+ default=None, description="Whether the watchlist is publicly visible."
+ )
+ __properties: ClassVar[List[str]] = [
+ "id",
+ "createdAt",
+ "updatedAt",
+ "name",
+ "displayName",
+ "description",
+ "people",
+ "companies",
+ "organizationId",
+ "visible",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in people (list)
+ _items = []
+ if self.people:
+ for _item_people in self.people:
+ _items.append(
+ _item_people.to_dict() if _item_people is not None else None
+ )
+ _dict["people"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in companies (list)
+ _items = []
+ if self.companies:
+ for _item_companies in self.companies:
+ _items.append(
+ _item_companies.to_dict() if _item_companies is not None else None
+ )
+ _dict["companies"] = _items
+ # set to None if id (nullable) is None
+ # and model_fields_set contains the field
+ if self.id is None and "id" in self.model_fields_set:
+ _dict["id"] = None
+
+ # set to None if created_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.created_at is None and "created_at" in self.model_fields_set:
+ _dict["createdAt"] = None
+
+ # set to None if updated_at (nullable) is None
+ # and model_fields_set contains the field
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
+ _dict["updatedAt"] = None
+
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if display_name (nullable) is None
+ # and model_fields_set contains the field
+ if self.display_name is None and "display_name" in self.model_fields_set:
+ _dict["displayName"] = None
+
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if people (nullable) is None
+ # and model_fields_set contains the field
+ if self.people is None and "people" in self.model_fields_set:
+ _dict["people"] = None
+
+ # set to None if companies (nullable) is None
+ # and model_fields_set contains the field
+ if self.companies is None and "companies" in self.model_fields_set:
+ _dict["companies"] = None
+
+ # set to None if organization_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.organization_id is None and "organization_id" in self.model_fields_set:
+ _dict["organizationId"] = None
+
+ # set to None if visible (nullable) is None
+ # and model_fields_set contains the field
+ if self.visible is None and "visible" in self.model_fields_set:
+ _dict["visible"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "id": obj.get("id"),
+ "createdAt": obj.get("createdAt"),
+ "updatedAt": obj.get("updatedAt"),
+ "name": obj.get("name"),
+ "displayName": obj.get("displayName"),
+ "description": obj.get("description"),
+ "people": [
+ WatchlistPersonDto.from_dict(_item) for _item in obj["people"]
+ ]
+ if obj.get("people") is not None
+ else None,
+ "companies": [
+ WatchlistCompanyDto.from_dict(_item) for _item in obj["companies"]
+ ]
+ if obj.get("companies") is not None
+ else None,
+ "organizationId": obj.get("organizationId"),
+ "visible": obj.get("visible"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/watchlist_list_result.py b/src/perigon/models/watchlist_list_result.py
new file mode 100644
index 0000000..c1c7b29
--- /dev/null
+++ b/src/perigon/models/watchlist_list_result.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.watchlist_dto import WatchlistDto
+
+
+class WatchlistListResult(BaseModel):
+ """
+ Paginated list of watchlists.
+ """
+
+ total: StrictInt = Field(
+ description="Total number of matching watchlists across all pages."
+ )
+ data: List[WatchlistDto] = Field(
+ description="Watchlists returned for the requested page."
+ )
+ __properties: ClassVar[List[str]] = ["total", "data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistListResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistListResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "total": obj.get("total"),
+ "data": [WatchlistDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/perigon/models/watchlist_person.py b/src/perigon/models/watchlist_person.py
similarity index 85%
rename from perigon/models/watchlist_person.py
rename to src/perigon/models/watchlist_person.py
index 7f27224..47bbc2b 100644
--- a/perigon/models/watchlist_person.py
+++ b/src/perigon/models/watchlist_person.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,22 +20,31 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WatchlistPerson(BaseModel):
"""
Updated list of people to include in the watchlist
- """ # noqa: E501
+ """
name: Optional[StrictStr] = None
description: Optional[StrictStr] = None
aliases: Optional[List[StrictStr]] = None
wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
- __properties: ClassVar[List[str]] = ["name", "description", "aliases", "wikidataId"]
+ query: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "description",
+ "aliases",
+ "wikidataId",
+ "query",
+ ]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,8 +55,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -92,6 +99,11 @@ def to_dict(self) -> Dict[str, Any]:
if self.wikidata_id is None and "wikidata_id" in self.model_fields_set:
_dict["wikidataId"] = None
+ # set to None if query (nullable) is None
+ # and model_fields_set contains the field
+ if self.query is None and "query" in self.model_fields_set:
+ _dict["query"] = None
+
return _dict
@classmethod
@@ -109,6 +121,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"description": obj.get("description"),
"aliases": obj.get("aliases"),
"wikidataId": obj.get("wikidataId"),
+ "query": obj.get("query"),
}
)
return _obj
diff --git a/src/perigon/models/watchlist_person_dto.py b/src/perigon/models/watchlist_person_dto.py
new file mode 100644
index 0000000..8285166
--- /dev/null
+++ b/src/perigon/models/watchlist_person_dto.py
@@ -0,0 +1,127 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class WatchlistPersonDto(BaseModel):
+ """
+ People included in the watchlist.
+ """
+
+ name: Optional[StrictStr] = None
+ description: Optional[StrictStr] = None
+ wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
+ aliases: Optional[List[StrictStr]] = None
+ query: Optional[StrictStr] = None
+ __properties: ClassVar[List[str]] = [
+ "name",
+ "description",
+ "wikidataId",
+ "aliases",
+ "query",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistPersonDto from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # set to None if name (nullable) is None
+ # and model_fields_set contains the field
+ if self.name is None and "name" in self.model_fields_set:
+ _dict["name"] = None
+
+ # set to None if description (nullable) is None
+ # and model_fields_set contains the field
+ if self.description is None and "description" in self.model_fields_set:
+ _dict["description"] = None
+
+ # set to None if wikidata_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.wikidata_id is None and "wikidata_id" in self.model_fields_set:
+ _dict["wikidataId"] = None
+
+ # set to None if aliases (nullable) is None
+ # and model_fields_set contains the field
+ if self.aliases is None and "aliases" in self.model_fields_set:
+ _dict["aliases"] = None
+
+ # set to None if query (nullable) is None
+ # and model_fields_set contains the field
+ if self.query is None and "query" in self.model_fields_set:
+ _dict["query"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistPersonDto from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "name": obj.get("name"),
+ "description": obj.get("description"),
+ "wikidataId": obj.get("wikidataId"),
+ "aliases": obj.get("aliases"),
+ "query": obj.get("query"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/watchlist_resolve_result.py b/src/perigon/models/watchlist_resolve_result.py
new file mode 100644
index 0000000..9cfad3b
--- /dev/null
+++ b/src/perigon/models/watchlist_resolve_result.py
@@ -0,0 +1,98 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.watchlist_dto import WatchlistDto
+
+
+class WatchlistResolveResult(BaseModel):
+ """
+ Resolved watchlists response.
+ """
+
+ data: List[WatchlistDto] = Field(description="Resolved watchlists.")
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistResolveResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in data (list)
+ _items = []
+ if self.data:
+ for _item_data in self.data:
+ _items.append(_item_data.to_dict() if _item_data is not None else None)
+ _dict["data"] = _items
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistResolveResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": [WatchlistDto.from_dict(_item) for _item in obj["data"]]
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/watchlist_result.py b/src/perigon/models/watchlist_result.py
new file mode 100644
index 0000000..b0e93d6
--- /dev/null
+++ b/src/perigon/models/watchlist_result.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.watchlist_dto import WatchlistDto
+
+
+class WatchlistResult(BaseModel):
+ """
+ Single watchlist response.
+ """
+
+ data: WatchlistDto
+ __properties: ClassVar[List[str]] = ["data"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of WatchlistResult from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of data
+ if self.data:
+ _dict["data"] = self.data.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of WatchlistResult from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "data": WatchlistDto.from_dict(obj["data"])
+ if obj.get("data") is not None
+ else None
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/watchlist_sort_by.py b/src/perigon/models/watchlist_sort_by.py
new file mode 100644
index 0000000..0b30174
--- /dev/null
+++ b/src/perigon/models/watchlist_sort_by.py
@@ -0,0 +1,39 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+from enum import Enum
+
+from typing_extensions import Self
+
+
+class WatchlistSortBy(str, Enum):
+ """
+ WatchlistSortBy
+ """
+
+ """
+ allowed enum values
+ """
+ ID = "id"
+ CREATEDAT = "createdAt"
+ UPDATEDAT = "updatedAt"
+ NAME = "name"
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of WatchlistSortBy from a JSON string"""
+ return cls(json.loads(json_str))
diff --git a/perigon/models/web_resources.py b/src/perigon/models/web_resources.py
similarity index 97%
rename from perigon/models/web_resources.py
rename to src/perigon/models/web_resources.py
index 730fc19..6176fd1 100644
--- a/perigon/models/web_resources.py
+++ b/src/perigon/models/web_resources.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WebResources(BaseModel):
"""
WebResources
- """ # noqa: E501
+ """
careers: Optional[StrictStr] = None
about: Optional[StrictStr] = None
@@ -67,7 +67,8 @@ class WebResources(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -78,8 +79,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/wiki_data.py b/src/perigon/models/wiki_data.py
similarity index 93%
rename from perigon/models/wiki_data.py
rename to src/perigon/models/wiki_data.py
index 5429d68..2533343 100644
--- a/perigon/models/wiki_data.py
+++ b/src/perigon/models/wiki_data.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.wikidata_label_holder import WikidataLabelHolder
@@ -29,7 +29,7 @@
class WikiData(BaseModel):
"""
WikiData
- """ # noqa: E501
+ """
page_id: Optional[StrictStr] = Field(default=None, alias="pageId")
section_id: Optional[StrictStr] = Field(default=None, alias="sectionId")
@@ -69,7 +69,8 @@ class WikiData(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -80,8 +81,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -109,8 +109,11 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.wikidata_instance_of:
for _item_wikidata_instance_of in self.wikidata_instance_of:
- if _item_wikidata_instance_of:
- _items.append(_item_wikidata_instance_of.to_dict())
+ _items.append(
+ _item_wikidata_instance_of.to_dict()
+ if _item_wikidata_instance_of is not None
+ else None
+ )
_dict["wikidataInstanceOf"] = _items
# set to None if page_id (nullable) is None
# and model_fields_set contains the field
@@ -218,14 +221,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"wikiNamespace": obj.get("wikiNamespace"),
"wikiTitle": obj.get("wikiTitle"),
"wikidataId": obj.get("wikidataId"),
- "wikidataInstanceOf": (
- [
- WikidataLabelHolder.from_dict(_item)
- for _item in obj["wikidataInstanceOf"]
- ]
- if obj.get("wikidataInstanceOf") is not None
- else None
- ),
+ "wikidataInstanceOf": [
+ WikidataLabelHolder.from_dict(_item)
+ for _item in obj["wikidataInstanceOf"]
+ ]
+ if obj.get("wikidataInstanceOf") is not None
+ else None,
"redirectTitles": obj.get("redirectTitles"),
"pageviews": obj.get("pageviews"),
"title": obj.get("title"),
diff --git a/perigon/models/wiki_page.py b/src/perigon/models/wiki_page.py
similarity index 90%
rename from perigon/models/wiki_page.py
rename to src/perigon/models/wiki_page.py
index 5a82382..53ba506 100644
--- a/perigon/models/wiki_page.py
+++ b/src/perigon/models/wiki_page.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.wiki_page_section_holder import WikiPageSectionHolder
@@ -30,7 +30,7 @@
class WikiPage(BaseModel):
"""
WikiPage
- """ # noqa: E501
+ """
id: Optional[StrictStr] = None
scraped_at: Optional[StrictStr] = Field(default=None, alias="scrapedAt")
@@ -80,7 +80,8 @@ class WikiPage(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -91,8 +92,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -120,15 +120,19 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.wikidata_instance_of:
for _item_wikidata_instance_of in self.wikidata_instance_of:
- if _item_wikidata_instance_of:
- _items.append(_item_wikidata_instance_of.to_dict())
+ _items.append(
+ _item_wikidata_instance_of.to_dict()
+ if _item_wikidata_instance_of is not None
+ else None
+ )
_dict["wikidataInstanceOf"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in sections (list)
_items = []
if self.sections:
for _item_sections in self.sections:
- if _item_sections:
- _items.append(_item_sections.to_dict())
+ _items.append(
+ _item_sections.to_dict() if _item_sections is not None else None
+ )
_dict["sections"] = _items
# set to None if id (nullable) is None
# and model_fields_set contains the field
@@ -258,24 +262,19 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"url": obj.get("url"),
"topImage": obj.get("topImage"),
"wikidataId": obj.get("wikidataId"),
- "wikidataInstanceOf": (
- [
- WikidataLabelHolder.from_dict(_item)
- for _item in obj["wikidataInstanceOf"]
- ]
- if obj.get("wikidataInstanceOf") is not None
- else None
- ),
+ "wikidataInstanceOf": [
+ WikidataLabelHolder.from_dict(_item)
+ for _item in obj["wikidataInstanceOf"]
+ ]
+ if obj.get("wikidataInstanceOf") is not None
+ else None,
"redirectTitles": obj.get("redirectTitles"),
"summary": obj.get("summary"),
- "sections": (
- [
- WikiPageSectionHolder.from_dict(_item)
- for _item in obj["sections"]
- ]
- if obj.get("sections") is not None
- else None
- ),
+ "sections": [
+ WikiPageSectionHolder.from_dict(_item) for _item in obj["sections"]
+ ]
+ if obj.get("sections") is not None
+ else None,
"categories": obj.get("categories"),
"externalLinks": obj.get("externalLinks"),
"references": obj.get("references"),
diff --git a/perigon/models/wiki_page_section_holder.py b/src/perigon/models/wiki_page_section_holder.py
similarity index 95%
rename from perigon/models/wiki_page_section_holder.py
rename to src/perigon/models/wiki_page_section_holder.py
index cf0fab7..1141f35 100644
--- a/perigon/models/wiki_page_section_holder.py
+++ b/src/perigon/models/wiki_page_section_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,13 +20,14 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WikiPageSectionHolder(BaseModel):
"""
WikiPageSectionHolder
- """ # noqa: E501
+ """
id: Optional[StrictStr] = None
title: Optional[StrictStr] = None
@@ -45,7 +45,8 @@ class WikiPageSectionHolder(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -56,8 +57,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/wikidata_date_holder.py b/src/perigon/models/wikidata_date_holder.py
similarity index 93%
rename from perigon/models/wikidata_date_holder.py
rename to src/perigon/models/wikidata_date_holder.py
index 29a4af3..ad6dba6 100644
--- a/perigon/models/wikidata_date_holder.py
+++ b/src/perigon/models/wikidata_date_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,20 +20,22 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WikidataDateHolder(BaseModel):
"""
WikidataDateHolder
- """ # noqa: E501
+ """
time: Optional[StrictStr] = None
precision: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = ["time", "precision"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +46,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/wikidata_label_holder.py b/src/perigon/models/wikidata_label_holder.py
similarity index 93%
rename from perigon/models/wikidata_label_holder.py
rename to src/perigon/models/wikidata_label_holder.py
index 7e95a08..08b1a10 100644
--- a/perigon/models/wikidata_label_holder.py
+++ b/src/perigon/models/wikidata_label_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,20 +20,22 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WikidataLabelHolder(BaseModel):
"""
WikidataLabelHolder
- """ # noqa: E501
+ """
wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
label: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = ["wikidataId", "label"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -45,8 +46,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
diff --git a/perigon/models/wikidata_political_party_holder.py b/src/perigon/models/wikidata_political_party_holder.py
similarity index 87%
rename from perigon/models/wikidata_political_party_holder.py
rename to src/perigon/models/wikidata_political_party_holder.py
index 6d1f189..760e7fe 100644
--- a/perigon/models/wikidata_political_party_holder.py
+++ b/src/perigon/models/wikidata_political_party_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.wikidata_date_holder import WikidataDateHolder
@@ -29,7 +29,7 @@
class WikidataPoliticalPartyHolder(BaseModel):
"""
WikidataPoliticalPartyHolder
- """ # noqa: E501
+ """
wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
label: Optional[StrictStr] = None
@@ -38,7 +38,8 @@ class WikidataPoliticalPartyHolder(BaseModel):
__properties: ClassVar[List[str]] = ["wikidataId", "label", "startTime", "endTime"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -49,8 +50,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -115,16 +115,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"wikidataId": obj.get("wikidataId"),
"label": obj.get("label"),
- "startTime": (
- WikidataDateHolder.from_dict(obj["startTime"])
- if obj.get("startTime") is not None
- else None
- ),
- "endTime": (
- WikidataDateHolder.from_dict(obj["endTime"])
- if obj.get("endTime") is not None
- else None
- ),
+ "startTime": WikidataDateHolder.from_dict(obj["startTime"])
+ if obj.get("startTime") is not None
+ else None,
+ "endTime": WikidataDateHolder.from_dict(obj["endTime"])
+ if obj.get("endTime") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/wikidata_position_holder.py b/src/perigon/models/wikidata_position_holder.py
similarity index 85%
rename from perigon/models/wikidata_position_holder.py
rename to src/perigon/models/wikidata_position_holder.py
index f6ed0bb..57cba9c 100644
--- a/perigon/models/wikidata_position_holder.py
+++ b/src/perigon/models/wikidata_position_holder.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.wikidata_date_holder import WikidataDateHolder
@@ -30,7 +30,7 @@
class WikidataPositionHolder(BaseModel):
"""
WikidataPositionHolder
- """ # noqa: E501
+ """
wikidata_id: Optional[StrictStr] = Field(default=None, alias="wikidataId")
label: Optional[StrictStr] = None
@@ -46,7 +46,8 @@ class WikidataPositionHolder(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -57,8 +58,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -131,21 +131,15 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"wikidataId": obj.get("wikidataId"),
"label": obj.get("label"),
- "startTime": (
- WikidataDateHolder.from_dict(obj["startTime"])
- if obj.get("startTime") is not None
- else None
- ),
- "endTime": (
- WikidataDateHolder.from_dict(obj["endTime"])
- if obj.get("endTime") is not None
- else None
- ),
- "employer": (
- WikidataLabelHolder.from_dict(obj["employer"])
- if obj.get("employer") is not None
- else None
- ),
+ "startTime": WikidataDateHolder.from_dict(obj["startTime"])
+ if obj.get("startTime") is not None
+ else None,
+ "endTime": WikidataDateHolder.from_dict(obj["endTime"])
+ if obj.get("endTime") is not None
+ else None,
+ "employer": WikidataLabelHolder.from_dict(obj["employer"])
+ if obj.get("employer") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/wikipedia_search_filter.py b/src/perigon/models/wikipedia_search_filter.py
similarity index 86%
rename from perigon/models/wikipedia_search_filter.py
rename to src/perigon/models/wikipedia_search_filter.py
index 5a3a665..f331ea8 100644
--- a/perigon/models/wikipedia_search_filter.py
+++ b/src/perigon/models/wikipedia_search_filter.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,73 +20,86 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
class WikipediaSearchFilter(BaseModel):
"""
Complex filter structure for Wikipedia page searches that supports nested logical operations (AND, OR, NOT) and multiple filtering criteria.
- """ # noqa: E501
+ """
page_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific Perigon page identifiers. Accepts either a single ID or an array of IDs. Returns only pages matching these IDs.",
alias="pageId",
+ json_schema_extra={"examples": [[""]]},
)
section_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific section identifiers. Accepts either a single ID or an array of IDs. Returns only pages containing these sections.",
alias="sectionId",
+ json_schema_extra={"examples": [[""]]},
)
wiki_page_id: Optional[List[StrictInt]] = Field(
default=None,
description="Filter by specific Wikipedia page identifiers. Accepts either a single ID or an array of IDs. Returns only pages matching these IDs.",
alias="wikiPageId",
+ json_schema_extra={"examples": [[0]]},
)
wiki_revision_id: Optional[List[StrictInt]] = Field(
default=None,
description="Filter by specific Perigon page revision identifiers. Accepts either a single ID or an array of IDs. Returns only pages matching these IDs.",
alias="wikiRevisionId",
+ json_schema_extra={"examples": [[0]]},
)
wiki_code: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific Wikipedia project codes. Returns only pages matching these projects.",
alias="wikiCode",
+ json_schema_extra={"examples": [[""]]},
)
wiki_namespace: Optional[List[StrictInt]] = Field(
default=None,
description="Filter by specific Wikipedia namespaces. Returns only pages matching these namespaces.",
alias="wikiNamespace",
+ json_schema_extra={"examples": [[0]]},
)
wikidata_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific Wikidata entity IDs. Returns only pages whose Wikidata entities match those ids.",
alias="wikidataId",
+ json_schema_extra={"examples": [[""]]},
)
wikidata_instance_of_id: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific Wikidata entity IDs. Returns only pages whose Wikidata entities are instances of provided ids.",
alias="wikidataInstanceOfId",
+ json_schema_extra={"examples": [[""]]},
)
wikidata_instance_of_label: Optional[List[StrictStr]] = Field(
default=None,
description="Filter by specific Wikidata entity labels. Returns only pages whose Wikidata entities are instances of these labels.",
alias="wikidataInstanceOfLabel",
+ json_schema_extra={"examples": [[""]]},
)
var_and: Optional[List[WikipediaSearchFilter]] = Field(
default=None,
description="Adds additional AND filter objects. These objects must be of the same type as the original filter object and will be combined with the existing filter using the AND logical operator.",
alias="AND",
+ json_schema_extra={"examples": [[{}]]},
)
var_or: Optional[List[WikipediaSearchFilter]] = Field(
default=None,
description="Adds additional OR filter objects. These objects must be of the same type as the original filter object and will be combined with the existing filter using the OR logical operator.",
alias="OR",
+ json_schema_extra={"examples": [[{}]]},
)
var_not: Optional[List[WikipediaSearchFilter]] = Field(
default=None,
description="A filter object for logical NOT operations",
alias="NOT",
+ json_schema_extra={"examples": [[{}]]},
)
__properties: ClassVar[List[str]] = [
"pageId",
@@ -105,7 +117,8 @@ class WikipediaSearchFilter(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -116,8 +129,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -145,22 +157,25 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.var_and:
for _item_var_and in self.var_and:
- if _item_var_and:
- _items.append(_item_var_and.to_dict())
+ _items.append(
+ _item_var_and.to_dict() if _item_var_and is not None else None
+ )
_dict["AND"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in var_or (list)
_items = []
if self.var_or:
for _item_var_or in self.var_or:
- if _item_var_or:
- _items.append(_item_var_or.to_dict())
+ _items.append(
+ _item_var_or.to_dict() if _item_var_or is not None else None
+ )
_dict["OR"] = _items
# override the default output from pydantic by calling `to_dict()` of each item in var_not (list)
_items = []
if self.var_not:
for _item_var_not in self.var_not:
- if _item_var_not:
- _items.append(_item_var_not.to_dict())
+ _items.append(
+ _item_var_not.to_dict() if _item_var_not is not None else None
+ )
_dict["NOT"] = _items
# set to None if page_id (nullable) is None
# and model_fields_set contains the field
@@ -253,21 +268,15 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"wikidataId": obj.get("wikidataId"),
"wikidataInstanceOfId": obj.get("wikidataInstanceOfId"),
"wikidataInstanceOfLabel": obj.get("wikidataInstanceOfLabel"),
- "AND": (
- [WikipediaSearchFilter.from_dict(_item) for _item in obj["AND"]]
- if obj.get("AND") is not None
- else None
- ),
- "OR": (
- [WikipediaSearchFilter.from_dict(_item) for _item in obj["OR"]]
- if obj.get("OR") is not None
- else None
- ),
- "NOT": (
- [WikipediaSearchFilter.from_dict(_item) for _item in obj["NOT"]]
- if obj.get("NOT") is not None
- else None
- ),
+ "AND": [WikipediaSearchFilter.from_dict(_item) for _item in obj["AND"]]
+ if obj.get("AND") is not None
+ else None,
+ "OR": [WikipediaSearchFilter.from_dict(_item) for _item in obj["OR"]]
+ if obj.get("OR") is not None
+ else None,
+ "NOT": [WikipediaSearchFilter.from_dict(_item) for _item in obj["NOT"]]
+ if obj.get("NOT") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/wikipedia_search_params.py b/src/perigon/models/wikipedia_search_params.py
similarity index 89%
rename from perigon/models/wikipedia_search_params.py
rename to src/perigon/models/wikipedia_search_params.py
index 6cef98e..f6bb5d8 100644
--- a/perigon/models/wikipedia_search_params.py
+++ b/src/perigon/models/wikipedia_search_params.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -22,6 +21,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Annotated, Self
from perigon.models.wikipedia_search_filter import WikipediaSearchFilter
@@ -30,31 +30,36 @@
class WikipediaSearchParams(BaseModel):
"""
WikipediaSearchParams
- """ # noqa: E501
+ """
prompt: Annotated[str, Field(min_length=0, strict=True, max_length=1024)] = Field(
- description="Natural language query to search the Wikipedia pages database"
+ description="Natural language query to search the Wikipedia pages database",
+ json_schema_extra={"examples": ["Enter your prompt"]},
)
filter: Optional[WikipediaSearchFilter] = None
wiki_revision_from: Optional[datetime] = Field(
default=None,
description="'wikiRevisionFrom' filter, will search pages modified after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Date time in ISO format, ie. 2024-01-01T00:00:00.",
alias="wikiRevisionFrom",
+ json_schema_extra={"examples": ["2024-01-01T00:00:00Z"]},
)
wiki_revision_to: Optional[datetime] = Field(
default=None,
description="'wikiRevisionFrom' filter, will search pages modified before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Date time in ISO format, ie. 2024-01-01T00:00:00.",
alias="wikiRevisionTo",
+ json_schema_extra={"examples": ["2024-12-31T23:59:59Z"]},
)
pageviews_from: Optional[StrictInt] = Field(
default=None,
description="'pageviewsFrom' filter, will search pages with at least the provided number of views per day.",
alias="pageviewsFrom",
+ json_schema_extra={"examples": [0]},
)
pageviews_to: Optional[StrictInt] = Field(
default=None,
description="'pageviewsFrom' filter, will search pages with at most the provided number of views per day.",
alias="pageviewsTo",
+ json_schema_extra={"examples": [0]},
)
size: Optional[Annotated[int, Field(le=100, strict=True, ge=1)]] = Field(
default=10, description="The number of items per page."
@@ -74,7 +79,8 @@ class WikipediaSearchParams(BaseModel):
]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -85,8 +91,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -168,17 +173,15 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"prompt": obj.get("prompt"),
- "filter": (
- WikipediaSearchFilter.from_dict(obj["filter"])
- if obj.get("filter") is not None
- else None
- ),
+ "filter": WikipediaSearchFilter.from_dict(obj["filter"])
+ if obj.get("filter") is not None
+ else None,
"wikiRevisionFrom": obj.get("wikiRevisionFrom"),
"wikiRevisionTo": obj.get("wikiRevisionTo"),
"pageviewsFrom": obj.get("pageviewsFrom"),
"pageviewsTo": obj.get("pageviewsTo"),
- "size": obj.get("size") if obj.get("size") is not None else 10,
- "page": obj.get("page") if obj.get("page") is not None else 0,
+ "size": obj.get("size") if "size" in obj else 10,
+ "page": obj.get("page") if "page" in obj else 0,
}
)
return _obj
diff --git a/perigon/models/wikipedia_search_result.py b/src/perigon/models/wikipedia_search_result.py
similarity index 85%
rename from perigon/models/wikipedia_search_result.py
rename to src/perigon/models/wikipedia_search_result.py
index fed4b9d..03e9d84 100644
--- a/perigon/models/wikipedia_search_result.py
+++ b/src/perigon/models/wikipedia_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, Field, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.wiki_page import WikiPage
@@ -29,7 +29,7 @@
class WikipediaSearchResult(BaseModel):
"""
Wikipedia search result
- """ # noqa: E501
+ """
status: StrictInt
num_results: StrictInt = Field(alias="numResults")
@@ -37,7 +37,8 @@ class WikipediaSearchResult(BaseModel):
__properties: ClassVar[List[str]] = ["status", "numResults", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -48,8 +49,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -77,8 +77,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -95,11 +96,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
{
"status": obj.get("status"),
"numResults": obj.get("numResults"),
- "results": (
- [WikiPage.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [WikiPage.from_dict(_item) for _item in obj["results"]]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/perigon/models/wikipedia_vector_search_result.py b/src/perigon/models/wikipedia_vector_search_result.py
similarity index 84%
rename from perigon/models/wikipedia_vector_search_result.py
rename to src/perigon/models/wikipedia_vector_search_result.py
index 4a523d0..b5b3c91 100644
--- a/perigon/models/wikipedia_vector_search_result.py
+++ b/src/perigon/models/wikipedia_vector_search_result.py
@@ -10,8 +10,7 @@
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
-""" # noqa: E501
-
+"""
from __future__ import annotations
@@ -21,6 +20,7 @@
from typing import Any, ClassVar, Dict, List, Optional, Set
from pydantic import BaseModel, ConfigDict, StrictInt
+from pydantic_core import to_jsonable_python
from typing_extensions import Self
from perigon.models.scored_data_wiki_data import ScoredDataWikiData
@@ -29,14 +29,15 @@
class WikipediaVectorSearchResult(BaseModel):
"""
Wikipedia vector search result
- """ # noqa: E501
+ """
status: StrictInt
results: List[ScoredDataWikiData]
__properties: ClassVar[List[str]] = ["status", "results"]
model_config = ConfigDict(
- populate_by_name=True,
+ validate_by_name=True,
+ validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),
)
@@ -47,8 +48,7 @@ def to_str(self) -> str:
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
- # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
- return json.dumps(self.to_dict())
+ return json.dumps(to_jsonable_python(self.to_dict()))
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
@@ -76,8 +76,9 @@ def to_dict(self) -> Dict[str, Any]:
_items = []
if self.results:
for _item_results in self.results:
- if _item_results:
- _items.append(_item_results.to_dict())
+ _items.append(
+ _item_results.to_dict() if _item_results is not None else None
+ )
_dict["results"] = _items
return _dict
@@ -93,11 +94,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate(
{
"status": obj.get("status"),
- "results": (
- [ScoredDataWikiData.from_dict(_item) for _item in obj["results"]]
- if obj.get("results") is not None
- else None
- ),
+ "results": [
+ ScoredDataWikiData.from_dict(_item) for _item in obj["results"]
+ ]
+ if obj.get("results") is not None
+ else None,
}
)
return _obj
diff --git a/src/perigon/models/x_post.py b/src/perigon/models/x_post.py
new file mode 100644
index 0000000..3789f5b
--- /dev/null
+++ b/src/perigon/models/x_post.py
@@ -0,0 +1,174 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from datetime import datetime
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.x_post_media import XPostMedia
+
+
+class XPost(BaseModel):
+ """
+ XPost
+ """
+
+ screen_name: StrictStr = Field(alias="screenName")
+ text: StrictStr
+ lang: StrictStr
+ created_at: datetime = Field(alias="createdAt")
+ avatar_url: Optional[StrictStr] = Field(default=None, alias="avatarUrl")
+ views: Optional[StrictInt] = None
+ favorites: Optional[StrictInt] = None
+ replies: Optional[StrictInt] = None
+ quotes: Optional[StrictInt] = None
+ bookmarks: Optional[StrictInt] = None
+ media: XPostMedia
+ x_post_id: Optional[StrictStr] = Field(default=None, alias="xPostId")
+ x_reposts: Optional[StrictInt] = Field(default=None, alias="xReposts")
+ __properties: ClassVar[List[str]] = [
+ "screenName",
+ "text",
+ "lang",
+ "createdAt",
+ "avatarUrl",
+ "views",
+ "favorites",
+ "replies",
+ "quotes",
+ "bookmarks",
+ "media",
+ "xPostId",
+ "xReposts",
+ ]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of XPost from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of media
+ if self.media:
+ _dict["media"] = self.media.to_dict()
+ # set to None if avatar_url (nullable) is None
+ # and model_fields_set contains the field
+ if self.avatar_url is None and "avatar_url" in self.model_fields_set:
+ _dict["avatarUrl"] = None
+
+ # set to None if views (nullable) is None
+ # and model_fields_set contains the field
+ if self.views is None and "views" in self.model_fields_set:
+ _dict["views"] = None
+
+ # set to None if favorites (nullable) is None
+ # and model_fields_set contains the field
+ if self.favorites is None and "favorites" in self.model_fields_set:
+ _dict["favorites"] = None
+
+ # set to None if replies (nullable) is None
+ # and model_fields_set contains the field
+ if self.replies is None and "replies" in self.model_fields_set:
+ _dict["replies"] = None
+
+ # set to None if quotes (nullable) is None
+ # and model_fields_set contains the field
+ if self.quotes is None and "quotes" in self.model_fields_set:
+ _dict["quotes"] = None
+
+ # set to None if bookmarks (nullable) is None
+ # and model_fields_set contains the field
+ if self.bookmarks is None and "bookmarks" in self.model_fields_set:
+ _dict["bookmarks"] = None
+
+ # set to None if x_post_id (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_post_id is None and "x_post_id" in self.model_fields_set:
+ _dict["xPostId"] = None
+
+ # set to None if x_reposts (nullable) is None
+ # and model_fields_set contains the field
+ if self.x_reposts is None and "x_reposts" in self.model_fields_set:
+ _dict["xReposts"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of XPost from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "screenName": obj.get("screenName"),
+ "text": obj.get("text"),
+ "lang": obj.get("lang"),
+ "createdAt": obj.get("createdAt"),
+ "avatarUrl": obj.get("avatarUrl"),
+ "views": obj.get("views"),
+ "favorites": obj.get("favorites"),
+ "replies": obj.get("replies"),
+ "quotes": obj.get("quotes"),
+ "bookmarks": obj.get("bookmarks"),
+ "media": XPostMedia.from_dict(obj["media"])
+ if obj.get("media") is not None
+ else None,
+ "xPostId": obj.get("xPostId"),
+ "xReposts": obj.get("xReposts"),
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/x_post_media.py b/src/perigon/models/x_post_media.py
new file mode 100644
index 0000000..eb9a389
--- /dev/null
+++ b/src/perigon/models/x_post_media.py
@@ -0,0 +1,122 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+from perigon.models.x_post_media_item import XPostMediaItem
+
+
+class XPostMedia(BaseModel):
+ """
+ XPostMedia
+ """
+
+ photo: Optional[List[XPostMediaItem]] = None
+ video: Optional[List[XPostMediaItem]] = None
+ __properties: ClassVar[List[str]] = ["photo", "video"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of XPostMedia from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of each item in photo (list)
+ _items = []
+ if self.photo:
+ for _item_photo in self.photo:
+ _items.append(
+ _item_photo.to_dict() if _item_photo is not None else None
+ )
+ _dict["photo"] = _items
+ # override the default output from pydantic by calling `to_dict()` of each item in video (list)
+ _items = []
+ if self.video:
+ for _item_video in self.video:
+ _items.append(
+ _item_video.to_dict() if _item_video is not None else None
+ )
+ _dict["video"] = _items
+ # set to None if photo (nullable) is None
+ # and model_fields_set contains the field
+ if self.photo is None and "photo" in self.model_fields_set:
+ _dict["photo"] = None
+
+ # set to None if video (nullable) is None
+ # and model_fields_set contains the field
+ if self.video is None and "video" in self.model_fields_set:
+ _dict["video"] = None
+
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of XPostMedia from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate(
+ {
+ "photo": [XPostMediaItem.from_dict(_item) for _item in obj["photo"]]
+ if obj.get("photo") is not None
+ else None,
+ "video": [XPostMediaItem.from_dict(_item) for _item in obj["video"]]
+ if obj.get("video") is not None
+ else None,
+ }
+ )
+ return _obj
diff --git a/src/perigon/models/x_post_media_item.py b/src/perigon/models/x_post_media_item.py
new file mode 100644
index 0000000..6f21c0c
--- /dev/null
+++ b/src/perigon/models/x_post_media_item.py
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+"""
+Perigon API
+
+The Perigon API provides access to comprehensive news and web content data. To use the API, simply sign up for a Perigon Business Solutions account to obtain your API key. Your available features may vary based on your plan. See the Authentication section for details on how to use your API key.
+
+The version of the OpenAPI document: 1.0.0
+Contact: data@perigon.io
+Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+Do not edit the class manually.
+"""
+
+from __future__ import annotations
+
+import json
+import pprint
+import re # noqa: F401
+from typing import Any, ClassVar, Dict, List, Optional, Set
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from pydantic_core import to_jsonable_python
+from typing_extensions import Self
+
+
+class XPostMediaItem(BaseModel):
+ """
+ XPostMediaItem
+ """
+
+ media_url_https: StrictStr = Field(alias="mediaUrlHttps")
+ __properties: ClassVar[List[str]] = ["mediaUrlHttps"]
+
+ model_config = ConfigDict(
+ validate_by_name=True,
+ validate_by_alias=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ return json.dumps(to_jsonable_python(self.to_dict()))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of XPostMediaItem from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of XPostMediaItem from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate({"mediaUrlHttps": obj.get("mediaUrlHttps")})
+ return _obj
diff --git a/perigon/py.typed b/src/perigon/py.typed
similarity index 100%
rename from perigon/py.typed
rename to src/perigon/py.typed
diff --git a/templates/README.mustache b/templates/README.mustache
deleted file mode 100644
index 8cabe56..0000000
--- a/templates/README.mustache
+++ /dev/null
@@ -1,271 +0,0 @@
-
-
-
-
-
-Perigon Python SDK
-Python client for the Perigon API
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-A modern, fullyβtyped Python SDK for the Perigon API, generated from the official OpenAPI specification.
-Works in **CPython 3.8+**, **PyPy**, serverless runtimes, notebooks, and async frameworks.
-
-## Table of Contents
-
-
-
----
-
-## β¨ Features
-
-- **Typeβhinted** request/response models powered by Pydantic
-- **Async and sync support** - choose the right approach for your application
-- Ships with **PEP 561 type hints** for excellent IDE integration
-- Generated directly from , so it's always in sync
-
----
-
-## π¦ Installation
-
-```bash
-pip install {{pypiName}}
-# poetry add {{pypiName}}
-# pipx install {{pypiName}}
-```
-
----
-
-## π Quick start
-
-### 1. Instantiate the client
-
-```python
-from perigon import V1Api, ApiClient
-
-# Create client with API key
-api = V1Api(ApiClient(api_key="YOUR_API_KEY"))
-
-# Alternative: environment variable or callable
-# api = V1Api(ApiClient(api_key=os.environ["PERIGON_API_KEY"]))
-# api = V1Api(ApiClient(api_key=lambda: get_api_key_from_vault()))
-```
-
-### 2. Make calls
-
-```python
-# π Search recent news articles (sync)
-articles = api.search_articles(q="artificial intelligence", size=5)
-print(articles.num_results, articles.articles[0].title)
-
-# π€ Look up a journalist by ID (sync)
-journalist = api.get_journalist_by_id(id="123456")
-print(journalist.name)
-
-# π Use async variant for async applications
-import asyncio
-
-async def fetch_data():
- # Search articles asynchronously
- articles = await api.search_articles_async(q="technology", size=5)
-
- # Look up journalist asynchronously
- journalist = await api.get_journalist_by_id_async(id="123456")
-
- return articles, journalist
-
-# Run in async context
-articles, journalist = asyncio.run(fetch_data())
-```
-
-> All methods return **typed objects** with full IDE autocompletion support.
-
----
-
-## π§βπ» Endpoint examples
-
-### Articles β search and filter news (`/v1/all`)
-
-**Docs β** [https://docs.perigon.io/docs/overview](https://docs.perigon.io/docs/overview)
-
-```python
-# Simple query
-articles = api.search_articles(q="technology", size=5)
-
-# With date range
-articles = api.search_articles(
- q="business",
- var_from="2025-04-01", # Note: 'from' is a reserved keyword in Python
- to="2025-04-08"
-)
-
-# Restrict to specific sources
-articles = api.search_articles(source=["nytimes.com"])
-```
-
-### Companies β fetch structured company data (`/v1/companies`)
-
-**Docs β** [https://docs.perigon.io/docs/company-data](https://docs.perigon.io/docs/company-data)
-
-```python
-results = api.search_companies(name="Apple", size=5)
-```
-
-### Journalists β search and detail lookβup (`/v1/journalists`)
-
-**Docs β** [https://docs.perigon.io/docs/journalist-data](https://docs.perigon.io/docs/journalist-data)
-
-```python
-# Search for journalists
-results = api.search_journalists1(name="Kevin", size=1)
-
-# Get detailed information
-journalist = api.get_journalist_by_id(id=results.journalists[0].id)
-```
-
-### Stories β discover related article clusters (`/v1/stories`)
-
-**Docs β** [https://docs.perigon.io/docs/stories-overview](https://docs.perigon.io/docs/stories-overview)
-
-```python
-stories = api.search_stories(q="climate change", size=5)
-```
-
-### Vector search β semantic retrieval (`/v1/vector`)
-
-**Docs β** [https://docs.perigon.io/docs/vector-endpoint](https://docs.perigon.io/docs/vector-endpoint)
-
-```python
-from perigon.models.article_search_params import ArticleSearchParams
-
-results = api.vector_search_articles(
- article_search_params=ArticleSearchParams(
- prompt="Latest advancements in artificial intelligence",
- size=5
- )
-)
-```
-
-### Summarizer β generate an instant summary (`/v1/summarizer`)
-
-**Docs β** [https://docs.perigon.io/docs/search-summarizer](https://docs.perigon.io/docs/search-summarizer)
-
-```python
-from perigon.models.summary_body import SummaryBody
-
-summary = api.search_summarizer(
- summary_body=SummaryBody(prompt="Key developments"),
- q="renewable energy",
- size=10
-).summary
-
-print(summary)
-```
-
-### Topics β explore taxonomy (`/v1/topics`)
-
-**Docs β** [https://docs.perigon.io/docs/topics](https://docs.perigon.io/docs/topics)
-
-```python
-topics = api.search_topics(size=10)
-```
-
-### Wikipedia β search and filter pages (`/v1/wikipedia`)
-
-**Docs β** [https://docs.perigon.io/docs/wikipedia](https://docs.perigon.io/docs/wikipedia)
-
-```python
-# Search Wikipedia pages
-wikipedia_result = api.search_wikipedia(
- q="machine learning",
- size=3,
- sort_by="relevance"
-)
-
-# Filter by specific criteria
-wikipedia_result = api.search_wikipedia(
- q="artificial intelligence",
- pageviews_from=100, # Only popular pages
-)
-```
-
-### Wikipedia vector search β semantic retrieval (`/v1/vector/wikipedia`)
-
-**Docs β** [https://docs.perigon.io/docs/vector-wikipedia](https://docs.perigon.io/docs/vector-wikipedia)
-
-```python
-from perigon.models.wikipedia_search_params import WikipediaSearchParams
-
-results = api.vector_search_wikipedia(
- wikipedia_search_params=WikipediaSearchParams(
- prompt="artificial intelligence and neural networks in computing",
- size=3,
- pageviews_from=100
- )
-)
-```
-
-| Action | Code Example |
-| --- | --- |
-| Filter by source | `api.search_articles(source=["nytimes.com"])` |
-| Limit by date range | `api.search_articles(q="business", var_from="2025-04-01", to="2025-04-08")` |
-| Company lookup | `api.search_companies(name="Apple", size=5)` |
-| Summarize any query | `api.search_summarizer(summary_body=SummaryBody(prompt="Key points"), q="renewable energy", size=20)` |
-| Semantic / vector search | `api.vector_search_articles(article_search_params=ArticleSearchParams(prompt="advancements in AI", size=5))` |
-| Retrieve available taxonomic topics | `api.search_topics(size=10)` |
-| Search Wikipedia pages | `api.search_wikipedia(q="machine learning", size=3, sort_by="relevance")` |
-| Wikipedia semantic search | `api.vector_search_wikipedia(wikipedia_search_params=WikipediaSearchParams(prompt="artificial intelligence", size=3))` |
-
----
-
-## π Async Support
-
-All methods have async counterparts with the `_async` suffix:
-
-```python
-import asyncio
-from perigon import V1Api, ApiClient
-
-async def main():
- api = V1Api(ApiClient(api_key="YOUR_API_KEY"))
-
- # Concurrent API calls
- articles_task = api.search_articles_async(q="technology", size=5)
- journalist_task = api.get_journalist_by_id_async(id="123456")
-
- # Gather results
- articles, journalist = await asyncio.gather(articles_task, journalist_task)
-
- return articles, journalist
-
-# Run the async function
-articles, journalist = asyncio.run(main())
-```
-
----
-
-## πͺͺ License
-
-MIT Β© Perigon
\ No newline at end of file
diff --git a/templates/__init__package.mustache b/templates/__init__package.mustache
index a06a76a..426616c 100644
--- a/templates/__init__package.mustache
+++ b/templates/__init__package.mustache
@@ -8,10 +8,12 @@ __version__ = "{{packageVersion}}"
# import apis into sdk package
{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classFilename}} import {{classname}}
+from {{apiPackage}}.{{classFilename}} import Async{{classname}}
{{/apis}}{{/apiInfo}}
# import ApiClient
from {{packageName}}.api_response import ApiResponse
-from {{packageName}}.api_client import ApiClient
+from {{packageName}}.api_client import ApiClient, AsyncApiClient
+from {{packageName}}.configuration import Configuration
from {{packageName}}.exceptions import OpenApiException
from {{packageName}}.exceptions import ApiTypeError
from {{packageName}}.exceptions import ApiValueError
diff --git a/templates/api.mustache b/templates/api.mustache
index b84a60c..8872f26 100644
--- a/templates/api.mustache
+++ b/templates/api.mustache
@@ -1,11 +1,16 @@
+from __future__ import annotations
+
+from datetime import datetime, timezone
from enum import Enum
-from typing import Any, Dict, Mapping, Iterable, Optional
+from typing import Any, Dict, Mapping, Optional
{{#packageName}}
-from {{packageName}}.api_client import ApiClient
+from {{packageName}}.api_client import ApiClient, AsyncApiClient, bind_client
+from {{packageName}}.configuration import Configuration
{{/packageName}}
{{^packageName}}
-from ..api_client import ApiClient
+from ..api_client import ApiClient, AsyncApiClient, bind_client
+from ..configuration import Configuration
{{/packageName}}
{{#imports}}
@@ -19,52 +24,83 @@ PATH_{{operationId.toUpperCase}} = "{{{path}}}"
{{/operation}}
{{/operations}}
+def _format_query_value(value: Any) -> str:
+ """Format one query value for the Perigon API."""
+ if isinstance(value, Enum):
+ value = value.value
+ if isinstance(value, datetime):
+ if value.tzinfo is not None:
+ value = value.astimezone(timezone.utc)
+ return value.strftime("%Y-%m-%dT%H:%M:%S")
+ if isinstance(value, bool):
+ return "true" if value else "false"
+ return str(value)
+
+
def _normalise_query(params: Mapping[str, Any]) -> Dict[str, Any]:
"""
- β’ Convert Enum β Enum.value
- β’ Convert list/tuple/set β CSV string (after Enum handling)
- β’ Skip None values
+ β’ Convert Enum to its value
+ β’ Format datetime as YYYY-MM-DDTHH:MM:SS in UTC, without an offset
+ β’ Format bool as true/false
+ β’ Keep list/tuple/set as a list so the key is repeated
+ β’ Skip None values and empty collections
"""
out: Dict[str, Any] = {}
for key, value in params.items():
- if value is None: # ignore "unset"
+ if value is None:
+ continue
+
+ if isinstance(value, (list, tuple, set)):
+ items = [_format_query_value(item) for item in value if item is not None]
+ if items:
+ out[key] = items
continue
- # Unwrap single Enum
- if isinstance(value, Enum): # Enum β str
- value = value.value
-
- # Handle datetime objects properly
- from datetime import datetime
-
- if isinstance(value, datetime):
- value = value.isoformat().split("+")[0]
-
- # Handle collection (after possible Enum unwrap)
- elif isinstance(value, (list, tuple, set)):
- # unwrap Enum members inside the collection
- items: Iterable[str] = (
- (
- item.isoformat().replace(" ", "+")
- if isinstance(item, datetime)
- else str(item.value if isinstance(item, Enum) else item)
- )
- for item in value
- )
- value = ",".join(items) # CSV join
- else:
- value = str(value)
-
- out[key] = value
+ out[key] = _format_query_value(value)
return out
+
+def _json_body(body: Any) -> Any:
+ """Serialize a request model.
+
+ Only fields the caller passed are included. An explicit null is kept so a
+ PATCH can clear that field. Unset defaults are omitted. mode="json" converts
+ datetime and UUID values, including inside nested models.
+ """
+ return body.model_dump(mode="json", by_alias=True, exclude_unset=True)
+
class {{classname}}:
"""{{{description}}}"""
-
- def __init__(self, api_client: Optional[ApiClient] = None):
- self.api_client = api_client or ApiClient()
+ def __init__(
+ self,
+ api_client: Optional[ApiClient] = None,
+ *,
+ api_key: Optional[str] = None,
+ base_url: Optional[str] = None,
+ timeout: Optional[float] = None,
+ configuration: Optional[Configuration] = None,
+ ) -> None:
+ self.api_client, self._owns_client = bind_client(
+ api_client,
+ ApiClient,
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ )
+
+ def __enter__(self) -> {{classname}}:
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ self.close()
+
+ def close(self) -> None:
+ """Close the HTTP client when this API created it."""
+ if self._owns_client:
+ self.api_client.close()
{{#operations}}
{{#operation}}
@@ -110,7 +146,7 @@ class {{classname}}:
"{{httpMethod}}",
path,
params=params{{#bodyParam}},
- json={{paramName}}.model_dump(by_alias=True, exclude_none=True),{{/bodyParam}}
+ json=_json_body({{paramName}}),{{/bodyParam}}
)
resp.raise_for_status()
{{#returnType}}
@@ -119,13 +155,52 @@ class {{classname}}:
{{^returnType}}
return resp.json()
{{/returnType}}
+{{/operation}}
+{{/operations}}
+
+class Async{{classname}}:
+ """Async {{{description}}}"""
+
+ def __init__(
+ self,
+ api_client: Optional[AsyncApiClient] = None,
+ *,
+ api_key: Optional[str] = None,
+ base_url: Optional[str] = None,
+ timeout: Optional[float] = None,
+ configuration: Optional[Configuration] = None,
+ ) -> None:
+ self.api_client, self._owns_client = bind_client(
+ api_client,
+ AsyncApiClient,
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ )
+
+ async def __aenter__(self) -> Async{{classname}}:
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
+ await self.close()
+
+ async def close(self) -> None:
+ """Close the HTTP client when this API created it."""
+ if self._owns_client:
+ await self.api_client.close()
+
+{{#operations}}
+{{#operation}}
# ----------------- {{operationId}} (async) ----------------- #
- async def {{operationId}}_async(
+ async def {{operationId}}(
self{{#allParams}}, {{paramName}}: {{^required}}Optional[{{/required}}{{dataType}}{{^required}}] = None{{/required}}{{/allParams}}
){{#returnType}} -> {{returnType}}{{/returnType}}:
"""
- Async variant of {{operationId}}. {{#notes}}{{notes}}{{/notes}}
+ {{#notes}}
+ {{notes}}
+ {{/notes}}
{{#allParams.0}}
Args:
@@ -142,7 +217,7 @@ class {{classname}}:
"""
# Get path template from class attribute
path = PATH_{{operationId.toUpperCase}}
-
+
{{#pathParams}}
# Replace path parameters
path = path.format({{{baseName}}}=str({{paramName}}))
@@ -155,11 +230,11 @@ class {{classname}}:
{{/queryParams}}
params = _normalise_query(params)
- resp = await self.api_client.request_async(
+ resp = await self.api_client.request(
"{{httpMethod}}",
path,
params=params{{#bodyParam}},
- json={{paramName}}.model_dump(by_alias=True, exclude_none=True),{{/bodyParam}}
+ json=_json_body({{paramName}}),{{/bodyParam}}
)
resp.raise_for_status()
{{#returnType}}
@@ -169,5 +244,4 @@ class {{classname}}:
return resp.json()
{{/returnType}}
{{/operation}}
-
{{/operations}}
diff --git a/templates/api_client.mustache b/templates/api_client.mustache
index d3cecc0..005a180 100644
--- a/templates/api_client.mustache
+++ b/templates/api_client.mustache
@@ -1,102 +1,210 @@
{{#packageName}}
-# PackageΒ : {{packageName}}
+# Package: {{packageName}}
{{/packageName}}
from __future__ import annotations
+from typing import Any, TypeVar
+
import httpx
-from typing import Any, Dict, Optional
-class ApiClient:
- """
- Single entryβpoint that wraps an httpx.Client *and* httpx.AsyncClient.
- Accepts api_key & base_url directly instead of a separate Configuration.
- """
+from {{packageName}}.configuration import DEFAULT_BASE_URL, Configuration
- def __init__(
- self,
- api_key: Optional[str] = None,
- base_url: str = "{{{basePath}}}",
- timeout: Optional[float] = None,
- ):
- self.api_key = api_key
- self.base_url = base_url or "{{{basePath}}}"
- self.timeout = timeout
+# The generator REST client uses this timeout when the caller does not set one.
+DEFAULT_ASYNC_TIMEOUT = 5 * 60
- # Persistent sessions for connectionβpool reuse (HTTP/1.1 or HTTP/2)
- self._sync = httpx.Client(base_url=self.base_url, timeout=self.timeout)
- self._async = httpx.AsyncClient(base_url=self.base_url, timeout=self.timeout)
+ClientT = TypeVar("ClientT", bound="ApiClient | AsyncApiClient")
- # ------------------------------------------------------------------ #
- # Internal helpers
- # ------------------------------------------------------------------ #
- def _auth_headers(self) -> Dict[str, str]:
- hdrs: Dict[str, str] = {}
+
+def _auth_headers(api_key: str | None) -> dict[str, str]:
+ headers: dict[str, str] = {}
+ if not api_key:
+ return headers
{{#authMethods}}
{{#isApiKey}}
{{#isKeyInHeader}}
- if self.api_key:
- hdrs["{{keyParamName}}"] = self.api_key
+ headers["{{keyParamName}}"] = api_key
{{/isKeyInHeader}}
{{/isApiKey}}
-
-{{#isBasicBearer}} {{! β Bearer token (type:http / scheme:bearer) }}
- if self.api_key: {{! or rename to access_token if you wish }}
- hdrs["Authorization"] = f"Bearer {self.api_key}"
+{{#isBasicBearer}}
+ headers["Authorization"] = f"Bearer {api_key}"
{{/isBasicBearer}}
-
-{{#isBearerToken}} {{! β synonym used by some generators β₯ v7.0.0 }}
- if self.api_key:
- hdrs["Authorization"] = f"Bearer {self.api_key}"
+{{#isBearerToken}}
+ headers["Authorization"] = f"Bearer {api_key}"
{{/isBearerToken}}
{{/authMethods}}
- return hdrs
+ return headers
+
+
+def _resolve_configuration(
+ api_key: str | None,
+ base_url: str | None,
+ configuration: Configuration | None,
+) -> Configuration:
+ if configuration is not None and (api_key is not None or base_url is not None):
+ raise ValueError("Pass either configuration or api_key and base_url, not both.")
+ if configuration is not None:
+ return configuration
+ return Configuration(api_key=api_key, base_url=base_url or DEFAULT_BASE_URL)
+
+
+class ApiClient:
+ """Sync HTTP client. Owns one ``httpx.Client`` and one connection pool."""
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+ ) -> None:
+ self.configuration = _resolve_configuration(api_key, base_url, configuration)
+ self.timeout = timeout
+ # The pool is created here. __enter__ does not enter the httpx client,
+ # because httpx only allows that before the first request.
+ self._client = httpx.Client(
+ base_url=self.configuration.base_url, timeout=self.timeout
+ )
- def _prepare_url(self, path: str) -> str:
- url = f"{self.base_url}{path}"
+ def request(
+ self,
+ method: str,
+ path: str,
+ headers: dict[str, str] | None = None,
+ **kwargs: Any,
+ ) -> httpx.Response:
+ all_headers = _auth_headers(self.configuration.api_key)
+ if headers:
+ all_headers.update(headers)
+ url = f"{self.configuration.base_url}{path}"
{{#authMethods}}
{{#isApiKey}}
-{{#isKeyInQuery}} if self.api_key:
+{{#isKeyInQuery}}
+ if self.configuration.api_key:
delimiter = "?" if "?" not in url else "&"
- url = f"{url}{delimiter}{{keyParamName}}={self.api_key}"
+ url = f"{url}{delimiter}{{keyParamName}}={self.configuration.api_key}"
{{/isKeyInQuery}}
{{/isApiKey}}
{{/authMethods}}
- return url
+ return self._client.request(method, url, headers=all_headers, **kwargs)
- # ------------------------------------------------------------------ #
- # Public request wrappers
- # ------------------------------------------------------------------ #
- def request(
+ def __enter__(self) -> ApiClient:
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ self.close()
+
+ def close(self) -> None:
+ self._client.close()
+
+
+class _AsyncRESTClient:
+ """Lazy async pool, following the generator REST client.
+
+ ``pool_manager`` stays ``None`` until the first request, which creates the
+ ``httpx.AsyncClient`` on the running loop.
+ """
+
+ def __init__(self, timeout: float) -> None:
+ self.timeout = timeout
+ self.pool_manager: httpx.AsyncClient | None = None
+
+ async def close(self) -> None:
+ if self.pool_manager is not None:
+ await self.pool_manager.aclose()
+
+ async def request(
self,
method: str,
- path: str,
- headers: Optional[Dict[str, str]] = None,
+ url: str,
+ headers: dict[str, str] | None = None,
**kwargs: Any,
) -> httpx.Response:
- all_headers = self._auth_headers()
- if headers: # merge userβprovided headers
- all_headers.update(headers)
- url = self._prepare_url(path)
- return self._sync.request(method, url, headers=all_headers, **kwargs)
+ if self.pool_manager is None:
+ self.pool_manager = httpx.AsyncClient()
+ kwargs.setdefault("timeout", self.timeout)
+ return await self.pool_manager.request(method, url, headers=headers, **kwargs)
+
+
+class AsyncApiClient:
+ """Async HTTP client. The pool is created on the first request."""
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+ ) -> None:
+ self.configuration = _resolve_configuration(api_key, base_url, configuration)
+ self.timeout = DEFAULT_ASYNC_TIMEOUT if timeout is None else timeout
+ self.rest_client = _AsyncRESTClient(self.timeout)
- async def request_async(
+ async def request(
self,
method: str,
path: str,
- headers: Optional[Dict[str, str]] = None,
+ headers: dict[str, str] | None = None,
**kwargs: Any,
) -> httpx.Response:
- all_headers = self._auth_headers()
+ all_headers = _auth_headers(self.configuration.api_key)
if headers:
all_headers.update(headers)
- url = self._prepare_url(path)
- return await self._async.request(method, url, headers=all_headers, **kwargs)
+ url = f"{self.configuration.base_url}{path}"
+{{#authMethods}}
+{{#isApiKey}}
+{{#isKeyInQuery}}
+ if self.configuration.api_key:
+ delimiter = "?" if "?" not in url else "&"
+ url = f"{url}{delimiter}{{keyParamName}}={self.configuration.api_key}"
+{{/isKeyInQuery}}
+{{/isApiKey}}
+{{/authMethods}}
+ return await self.rest_client.request(
+ method, url, headers=all_headers, **kwargs
+ )
+
+ async def __aenter__(self) -> AsyncApiClient:
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
+ await self.close()
+
+ async def close(self) -> None:
+ await self.rest_client.close()
- # ------------------------------------------------------------------ #
- # Cleanβup helpers
- # ------------------------------------------------------------------ #
- def close(self) -> None:
- self._sync.close()
- async def aclose(self) -> None:
- await self._async.aclose()
+def bind_client(
+ api_client: ClientT | None,
+ client_cls: type[ClientT],
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ timeout: float | None = None,
+ configuration: Configuration | None = None,
+) -> tuple[ClientT, bool]:
+ """Return a client and whether the caller owns it.
+
+ A passed client is borrowed. Keyword arguments create a client that the
+ caller must close. Passing both is rejected.
+ """
+ if api_client is not None and (
+ api_key is not None
+ or base_url is not None
+ or timeout is not None
+ or configuration is not None
+ ):
+ raise ValueError(
+ "Pass either an API client or api_key, base_url, timeout, "
+ "and configuration, not both."
+ )
+ if api_client is not None:
+ return api_client, False
+ return (
+ client_cls(
+ api_key=api_key,
+ base_url=base_url,
+ timeout=timeout,
+ configuration=configuration,
+ ),
+ True,
+ )
diff --git a/templates/configuration.mustache b/templates/configuration.mustache
new file mode 100644
index 0000000..586f8a0
--- /dev/null
+++ b/templates/configuration.mustache
@@ -0,0 +1,22 @@
+{{#packageName}}
+# Package: {{packageName}}
+{{/packageName}}
+"""Settings shared by the sync and async API clients."""
+
+DEFAULT_BASE_URL = "{{{basePath}}}"
+
+
+class Configuration:
+ """API key and host used to build a client.
+
+ :param api_key: Perigon API key. Sent as ``Authorization: Bearer``.
+ :param base_url: API host. Defaults to ``{{{basePath}}}``.
+ """
+
+ def __init__(
+ self,
+ api_key: str | None = None,
+ base_url: str = DEFAULT_BASE_URL,
+ ) -> None:
+ self.api_key = api_key
+ self.base_url = base_url or DEFAULT_BASE_URL
diff --git a/templates/pyproject.mustache b/templates/pyproject.mustache
deleted file mode 100644
index 445bbd7..0000000
--- a/templates/pyproject.mustache
+++ /dev/null
@@ -1,69 +0,0 @@
-[tool.poetry]
-name = "{{{packageName}}}"
-version = "0.0.0"
-description = "{{{appName}}}"
-authors = ["{{infoName}}{{^infoName}}Perigon Support{{/infoName}} <{{infoEmail}}{{^infoEmail}}data@perigon.io{{/infoEmail}}>"]
-license = "{{{licenseInfo}}}{{^licenseInfo}}NoLicense{{/licenseInfo}}"
-readme = "README.md"
-repository = "https://github.com/{{githubOrg}}/{{githubRepo}}"
-keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"]
-include = ["{{packageName}}/py.typed"]
-
-[tool.poetry.dependencies]
-python = "^3.9"
-
-# ---- core runtime dependencies ----
-httpx = ">= 0.27, < 1.0" {{! replaces urllib3 }}
-python-dateutil = ">= 2.8.2"
-pydantic = ">= 2"
-typing-extensions = ">= 4.7.1"
-
-{{#asyncio}}
-aiohttp = ">= 3.8.4"
-aiohttp-retry = ">= 2.8.3"
-{{/asyncio}}
-
-{{#hasHttpSignatureMethods}}
-pem = ">= 19.3.0"
-pycryptodome = ">= 3.9.0"
-{{/hasHttpSignatureMethods}}
-
-[tool.poetry.group.dev.dependencies]
-isort = ">= 6.0.1"
-black = ">=25.1.0"
-pytest = ">= 7.2.1"
-pytest-cov = ">= 2.8.1"
-tox = ">= 3.9.0"
-flake8 = ">= 4.0.0"
-types-python-dateutil = ">= 2.8.19.14"
-mypy = ">= 1.5"
-
-[tool.poetry-dynamic-versioning]
-enable = true # plugin only activates if this flag is on
-
-[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
-
-[tool.pylint.'MESSAGES CONTROL']
-extension-pkg-whitelist = "pydantic"
-
-[tool.mypy]
-files = [
- "{{packageName}}",
- "tests",
-]
-warn_unused_configs = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-strict_equality = true
-extra_checks = true
-check_untyped_defs = true
-disallow_subclassing_any = true
-disallow_untyped_decorators = true
-disallow_any_generics = true
-disallow_untyped_calls = true
-disallow_incomplete_defs = true
-disallow_untyped_defs = true
-no_implicit_reexport = true
-warn_return_any = true
diff --git a/templates/setup.mustache b/templates/setup.mustache
deleted file mode 100644
index a29a79b..0000000
--- a/templates/setup.mustache
+++ /dev/null
@@ -1,41 +0,0 @@
-# coding: utf-8
-
-{{>partial_header}}
-
-from setuptools import setup, find_packages # noqa: H301
-
-# To install the library, run the following
-#
-# python setup.py install
-#
-# prerequisite: setuptools
-# http://pypi.python.org/pypi/setuptools
-NAME = "{{{projectName}}}"
-VERSION = "{{packageVersion}}"
-PYTHON_REQUIRES = ">= 3.9"
-REQUIRES = [
- "urllib3 >= 2.1.0, < 3.0.0",
- "python-dateutil >= 2.8.2",
- "httpx >= 0.27, < 1.0",
- "pydantic >= 2",
- "typing-extensions >= 4.7.1",
-]
-
-setup(
- name=NAME,
- version=VERSION,
- description="{{appName}}",
- author="{{infoName}}{{^infoName}}OpenAPI Generator community{{/infoName}}",
- author_email="{{infoEmail}}{{^infoEmail}}team@openapitools.org{{/infoEmail}}",
- url="{{packageUrl}}",
- keywords=["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"],
- install_requires=REQUIRES,
- packages=find_packages(exclude=["test", "tests"]),
- include_package_data=True,
- {{#licenseInfo}}license="{{.}}",
- {{/licenseInfo}}long_description_content_type='text/markdown',
- long_description="""\
- {{appDescription}}
- """, # noqa: E501
- package_data={"{{{packageName}}}": ["py.typed"]},
-)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..d0cac2a
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,68 @@
+import os
+from collections.abc import Iterator
+
+import pytest
+import respx
+
+
+def pytest_addoption(parser: pytest.Parser) -> None:
+ parser.addoption(
+ "--integration",
+ action="store_true",
+ default=False,
+ help="Run live Perigon API tests. Requires PERIGON_API_KEY.",
+ )
+ parser.addoption(
+ "--unit",
+ action="store_true",
+ default=False,
+ help="With --integration, also run the offline tests.",
+ )
+
+
+def pytest_collection_modifyitems(
+ config: pytest.Config, items: list[pytest.Item]
+) -> None:
+ """Choose which suite runs.
+
+ The default run is offline. ``--integration`` runs the live tests.
+ ``--integration --unit`` runs both. ``uv run pytest`` stays offline.
+ """
+ integration = config.getoption("--integration")
+ unit = config.getoption("--unit")
+ if integration and not os.environ.get("PERIGON_API_KEY"):
+ raise pytest.UsageError("PERIGON_API_KEY must be set to run integration tests.")
+
+ def keep(item: pytest.Item) -> bool:
+ live = _is_integration(item)
+ if integration and unit:
+ return True
+ if integration:
+ return live
+ return not live
+
+ selected = [item for item in items if keep(item)]
+ deselected = [item for item in items if item not in selected]
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = selected
+
+
+def _is_integration(item: pytest.Item) -> bool:
+ return item.get_closest_marker("integration") is not None
+
+
+@pytest.fixture(autouse=True)
+def block_real_http(request: pytest.FixtureRequest) -> Iterator[None]:
+ """Fail any HTTP call that a test did not mock.
+
+ Integration tests opt out so they can call the Perigon API.
+ """
+ if request.node.get_closest_marker("integration") is not None:
+ yield
+ return
+ # No parentheses: routes added with respx.get/post/route join this router.
+ # Parentheses would open a separate router and leave those routes unused.
+ # assert_all_mocked is already on, so a request with no route fails.
+ with respx.mock:
+ yield
diff --git a/tests/samples.py b/tests/samples.py
new file mode 100644
index 0000000..0d58f85
--- /dev/null
+++ b/tests/samples.py
@@ -0,0 +1,221 @@
+"""Build the smallest values that satisfy generated Pydantic models.
+
+The endpoint sweep uses these so each method can parse a fake response and
+send a valid body without a recorded API payload.
+"""
+
+from __future__ import annotations
+
+import inspect
+import re
+import types
+from datetime import UTC, date, datetime
+from enum import Enum
+from typing import Annotated, Any, Union, get_args, get_origin
+from uuid import UUID
+
+from annotated_types import Ge, Gt, Le, Lt, MaxLen, MinLen
+from pydantic import BaseModel
+
+_ENUM_SET = re.compile(r"not in set\((\[.*?\])\)", re.DOTALL)
+_QUOTED = re.compile(r"""["']([^"']+)["']""")
+
+_MISSING = object()
+
+
+def sample_model(
+ model: type[BaseModel], seen: set[type[BaseModel]] | None = None
+) -> BaseModel:
+ """Return a valid instance with only required fields populated."""
+ if seen is None:
+ seen = set()
+ if model in seen:
+ return model.model_construct()
+ seen.add(model)
+ try:
+ kwargs: dict[str, Any] = {}
+ for name, field in model.model_fields.items():
+ if not field.is_required():
+ continue
+ kwargs[name] = sample_value(
+ field.annotation,
+ metadata=list(field.metadata),
+ model=model,
+ field_name=name,
+ seen=seen,
+ )
+ return model.model_validate(kwargs)
+ finally:
+ seen.discard(model)
+
+
+def sample_json(model: type[BaseModel]) -> dict[str, Any]:
+ """JSON object that ``model.model_validate`` accepts."""
+ data: dict[str, Any] = sample_model(model).model_dump(
+ mode="json", by_alias=True, exclude_none=True
+ )
+ model.model_validate(data)
+ return data
+
+
+def sample_argument(annotation: Any) -> Any:
+ """Smallest value for one required method parameter."""
+ return sample_value(annotation, seen=set())
+
+
+def sample_value(
+ annotation: Any,
+ *,
+ metadata: list[Any] | None = None,
+ model: type[BaseModel] | None = None,
+ field_name: str = "",
+ seen: set[type[BaseModel]] | None = None,
+ preferred: Any = _MISSING,
+) -> Any:
+ if seen is None:
+ seen = set()
+ if preferred is not _MISSING:
+ return preferred
+
+ annotation = _strip(annotation)
+ allowed = _enum_values(model, field_name) if model is not None else None
+ origin = get_origin(annotation)
+
+ if origin in (list, set, frozenset):
+ item_annotation = get_args(annotation)[0] if get_args(annotation) else str
+ item_preferred: Any = _MISSING
+ if allowed and _is_str(item_annotation):
+ item_preferred = allowed[0]
+ count = _min_len(metadata)
+ return [
+ sample_value(
+ item_annotation,
+ seen=seen,
+ preferred=item_preferred,
+ )
+ for _ in range(count)
+ ]
+
+ if origin is dict:
+ args = get_args(annotation)
+ if _min_len(metadata) == 0:
+ return {}
+ key_annotation = args[0] if args else str
+ value_annotation = args[1] if len(args) > 1 else str
+ key = sample_value(key_annotation, seen=seen)
+ return {str(key): sample_value(value_annotation, seen=seen)}
+
+ if isinstance(annotation, type) and issubclass(annotation, BaseModel):
+ return sample_model(annotation, seen)
+
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
+ return next(iter(annotation))
+
+ if _is_str(annotation):
+ return _text(metadata, allowed[0] if allowed else None)
+
+ if annotation is bool:
+ return True
+ if annotation is int:
+ return _number(metadata, as_float=False)
+ if annotation is float:
+ return _number(metadata, as_float=True)
+ if annotation is datetime:
+ return datetime(2024, 1, 2, 3, 4, 5, tzinfo=UTC)
+ if annotation is date:
+ return date(2024, 1, 2)
+ if annotation is UUID:
+ return UUID("00000000-0000-0000-0000-000000000001")
+ if annotation is bytes:
+ return b"x"
+ return "x"
+
+
+def _strip(annotation: Any) -> Any:
+ origin = get_origin(annotation)
+ if origin is Annotated:
+ return _strip(get_args(annotation)[0])
+ if origin is Union or origin is types.UnionType:
+ args = [item for item in get_args(annotation) if item is not type(None)]
+ if len(args) == 1:
+ return _strip(args[0])
+ stripped = [_strip(item) for item in args]
+ for preferred_type in (str, int, float, bool):
+ if preferred_type in stripped:
+ return preferred_type
+ return stripped[0]
+ return annotation
+
+
+def _is_str(annotation: Any) -> bool:
+ return _strip(annotation) is str
+
+
+def _min_len(metadata: list[Any] | None) -> int:
+ if not metadata:
+ return 0
+ for item in metadata:
+ if isinstance(item, MinLen):
+ return int(item.min_length)
+ return 0
+
+
+def _text(metadata: list[Any] | None, preferred: str | None) -> str:
+ if preferred is not None:
+ return preferred
+ low = 1
+ high: int | None = None
+ for item in metadata or []:
+ if isinstance(item, MinLen):
+ low = max(low, int(item.min_length))
+ if isinstance(item, MaxLen):
+ limit = int(item.max_length)
+ high = limit if high is None else min(high, limit)
+ if high is not None and low > high:
+ low = high
+ if low <= 0:
+ return "x" if high is None or high >= 1 else ""
+ return "x" * low
+
+
+def _as_float(value: Any) -> float:
+ return float(value)
+
+
+def _number(metadata: list[Any] | None, *, as_float: bool) -> int | float:
+ low = 0.0
+ high: float | None = None
+ for item in metadata or []:
+ if isinstance(item, Ge):
+ low = max(low, _as_float(item.ge))
+ elif isinstance(item, Gt):
+ low = max(low, _as_float(item.gt) + 1)
+ elif isinstance(item, Le):
+ limit = _as_float(item.le)
+ high = limit if high is None else min(high, limit)
+ elif isinstance(item, Lt):
+ limit = _as_float(item.lt) - 1
+ high = limit if high is None else min(high, limit)
+ chosen = low
+ if chosen == 0 and (high is None or high >= 1):
+ chosen = 1
+ if high is not None and chosen > high:
+ chosen = high
+ if as_float:
+ return float(chosen)
+ return int(chosen)
+
+
+def _enum_values(model: type[BaseModel], field_name: str) -> list[str] | None:
+ decorators = model.__pydantic_decorators__.field_validators
+ for decorator in decorators.values():
+ if field_name not in decorator.info.fields:
+ continue
+ source = inspect.getsource(decorator.func)
+ match = _ENUM_SET.search(source)
+ if match is None:
+ continue
+ values = _QUOTED.findall(match.group(1))
+ if values:
+ return values
+ return None
diff --git a/tests/test_api_client.py b/tests/test_api_client.py
new file mode 100644
index 0000000..0efaa42
--- /dev/null
+++ b/tests/test_api_client.py
@@ -0,0 +1,106 @@
+import httpx
+import pytest
+import respx
+
+from perigon import (
+ ApiClient,
+ AsyncApiClient,
+ AsyncV1Api,
+ Configuration,
+ V1Api,
+)
+
+
+def test_sync_context_manager_closes_client() -> None:
+ client = ApiClient()
+ with client:
+ assert not client._client.is_closed
+ assert client._client.is_closed
+ client.close()
+
+
+def test_owned_v1api_closes_client() -> None:
+ with V1Api(api_key="test") as api:
+ client = api.api_client
+ assert api._owns_client
+ assert not client._client.is_closed
+ assert client._client.is_closed
+
+
+def test_borrowed_v1api_leaves_client_open() -> None:
+ client = ApiClient(api_key="test")
+ with V1Api(client) as api:
+ assert api.api_client is client
+ assert not api._owns_client
+ assert not client._client.is_closed
+ client.close()
+
+
+def test_conflicting_api_arguments_raise() -> None:
+ client = ApiClient()
+ with pytest.raises(ValueError):
+ V1Api(client, api_key="test")
+ client.close()
+
+
+def test_conflicting_configuration_arguments_raise() -> None:
+ config = Configuration(api_key="test", base_url="https://example.com")
+ with pytest.raises(ValueError):
+ ApiClient(api_key="other", configuration=config)
+ client = ApiClient(configuration=config)
+ assert client.configuration is config
+ assert client.timeout is None
+ client.close()
+
+
+async def test_async_pool_is_created_on_first_request() -> None:
+ client = AsyncApiClient(timeout=1)
+ assert client.rest_client.pool_manager is None
+ await client.close()
+ respx.get("https://api.perigon.io/").mock(return_value=httpx.Response(200))
+ response = await client.request("GET", "/")
+ assert response.status_code == 200
+ pool = client.rest_client.pool_manager
+ assert pool is not None
+ assert not pool.is_closed
+ await client.close()
+ assert pool.is_closed
+
+
+async def test_async_context_manager_closes_pool() -> None:
+ respx.get("https://api.perigon.io/").mock(return_value=httpx.Response(200))
+ async with AsyncApiClient(timeout=1) as client:
+ response = await client.request("GET", "/")
+ assert response.status_code == 200
+ pool = client.rest_client.pool_manager
+ assert pool is not None
+ assert pool is not None
+ assert pool.is_closed
+
+
+async def test_owned_async_v1api_closes_pool() -> None:
+ respx.get("https://api.perigon.io/").mock(return_value=httpx.Response(200))
+ async with AsyncV1Api(api_key="test", timeout=1) as api:
+ assert api._owns_client
+ response = await api.api_client.request("GET", "/")
+ assert response.status_code == 200
+ pool = api.api_client.rest_client.pool_manager
+ assert pool is not None
+ assert not pool.is_closed
+ assert pool is not None
+ assert pool.is_closed
+
+
+async def test_borrowed_async_v1api_leaves_client_open() -> None:
+ client = AsyncApiClient(timeout=1)
+ async with AsyncV1Api(client) as api:
+ assert api.api_client is client
+ assert not api._owns_client
+ assert client.rest_client.pool_manager is None
+ await client.close()
+ assert client.rest_client.pool_manager is None
+
+
+def test_async_client_default_timeout() -> None:
+ client = AsyncApiClient()
+ assert client.timeout == 300
diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py
new file mode 100644
index 0000000..fa1887a
--- /dev/null
+++ b/tests/test_endpoints.py
@@ -0,0 +1,79 @@
+import inspect
+import re
+from typing import Any, get_type_hints
+
+import httpx
+import pytest
+import respx
+
+import perigon.api.v1_api as v1_module
+from perigon import AsyncV1Api, V1Api
+from tests.samples import sample_argument, sample_json
+
+_VERB = re.compile(r"""request\(\s*["']([A-Z]+)["']""")
+
+
+def _cases() -> list[tuple[type[V1Api] | type[AsyncV1Api], str]]:
+ cases: list[tuple[type[V1Api] | type[AsyncV1Api], str]] = []
+ for api_cls in (V1Api, AsyncV1Api):
+ for name, member in inspect.getmembers(api_cls, inspect.isfunction):
+ if name.startswith("_") or name == "close":
+ continue
+ cases.append((api_cls, name))
+ return cases
+
+
+def _case_id(api_cls: type[V1Api] | type[AsyncV1Api], name: str) -> str:
+ return f"{api_cls.__name__}.{name}"
+
+
+_ENDPOINT_CASES = _cases()
+
+
+@pytest.mark.parametrize(
+ ("api_cls", "method_name"),
+ _ENDPOINT_CASES,
+ ids=[_case_id(api_cls, name) for api_cls, name in _ENDPOINT_CASES],
+)
+async def test_method_sends_request_and_parses_response(
+ api_cls: type[V1Api] | type[AsyncV1Api], method_name: str
+) -> None:
+ method = getattr(api_cls, method_name)
+ hints = get_type_hints(method)
+ return_type = hints["return"]
+ payload = sample_json(return_type)
+ kwargs: dict[str, Any] = {}
+ for name, param in inspect.signature(method).parameters.items():
+ if name == "self" or param.default is not inspect.Parameter.empty:
+ continue
+ kwargs[name] = sample_argument(hints[name])
+
+ route = respx.route(host="api.perigon.io").mock(
+ return_value=httpx.Response(200, json=payload)
+ )
+ api = api_cls(api_key="test-key")
+ try:
+ result = getattr(api, method_name)(**kwargs)
+ if inspect.isawaitable(result):
+ result = await result
+ finally:
+ closed = api.close()
+ if inspect.isawaitable(closed):
+ await closed
+
+ request = route.calls.last.request
+ verb = _VERB.search(inspect.getsource(method))
+ assert verb is not None
+ assert request.method == verb.group(1)
+ assert request.url.path == _expected_path(method_name, kwargs)
+ assert request.headers["authorization"] == "Bearer test-key"
+ assert isinstance(result, return_type)
+
+
+def _expected_path(method_name: str, kwargs: dict[str, Any]) -> str:
+ path = getattr(v1_module, f"PATH_{method_name.upper()}")
+ for key, value in kwargs.items():
+ token = "{" + key + "}"
+ if token in path:
+ path = path.replace(token, str(value))
+ return path
diff --git a/tests/test_request_encoding.py b/tests/test_request_encoding.py
new file mode 100644
index 0000000..64e39ad
--- /dev/null
+++ b/tests/test_request_encoding.py
@@ -0,0 +1,95 @@
+import json
+from datetime import datetime, timedelta, timezone
+from uuid import UUID
+
+import httpx
+import pytest
+import respx
+
+from perigon import V1Api
+from perigon.models import Journalist, MonitorResult, QuerySearchResult, SummaryBody
+from perigon.models.summary_search_result import SummarySearchResult
+from tests.samples import sample_json
+
+
+def test_search_articles_encodes_query_and_auth() -> None:
+ route = respx.get("https://api.perigon.io/v1/articles/all").mock(
+ return_value=httpx.Response(200, json=sample_json(QuerySearchResult))
+ )
+ published_from = datetime(2024, 6, 1, 12, 0, tzinfo=timezone(timedelta(hours=2)))
+
+ with V1Api(api_key="test-key") as api:
+ result = api.search_articles(
+ q="technology",
+ size=5,
+ source=["nytimes.com", "wsj.com"],
+ var_from=published_from,
+ paywall=True,
+ title=None,
+ )
+
+ request = route.calls.last.request
+ assert request.method == "GET"
+ assert request.url.path == "/v1/articles/all"
+ assert request.headers["authorization"] == "Bearer test-key"
+ assert request.url.params["q"] == "technology"
+ assert request.url.params["size"] == "5"
+ assert request.url.params.get_list("source") == ["nytimes.com", "wsj.com"]
+ assert request.url.params["from"] == "2024-06-01T10:00:00"
+ assert request.url.params["paywall"] == "true"
+ assert "title" not in request.url.params
+ assert isinstance(result, QuerySearchResult)
+
+
+def test_search_summarizer_sends_only_set_body_fields() -> None:
+ route = respx.post("https://api.perigon.io/v1/summarize").mock(
+ return_value=httpx.Response(200, json=sample_json(SummarySearchResult))
+ )
+ body = SummaryBody(model="gpt-6-luna", max_article_count=3)
+
+ with V1Api(api_key="test-key") as api:
+ result = api.search_summarizer(summary_body=body)
+
+ request = route.calls.last.request
+ assert json.loads(request.content) == {"model": "gpt-6-luna", "maxArticleCount": 3}
+ assert isinstance(result, SummarySearchResult)
+
+
+def test_path_parameters_are_substituted() -> None:
+ journalist = respx.get("https://api.perigon.io/v1/journalists/jrn-1").mock(
+ return_value=httpx.Response(200, json=sample_json(Journalist))
+ )
+ monitor_id = UUID("00000000-0000-0000-0000-000000000001")
+ monitor = respx.post(
+ f"https://api.perigon.io/v1/api/monitors/{monitor_id}/activate"
+ ).mock(return_value=httpx.Response(200, json=sample_json(MonitorResult)))
+
+ with V1Api(api_key="test-key") as api:
+ found = api.get_journalist_by_id(id="jrn-1")
+ activated = api.activate_monitor_api(uuid=monitor_id)
+
+ assert journalist.calls.last.request.url.path == "/v1/journalists/jrn-1"
+ assert isinstance(found, Journalist)
+ assert monitor.calls.last.request.method == "POST"
+ assert monitor.calls.last.request.url.path == (
+ f"/v1/api/monitors/{monitor_id}/activate"
+ )
+ assert isinstance(activated, MonitorResult)
+
+
+def test_http_error_raises() -> None:
+ respx.get("https://api.perigon.io/v1/articles/all").mock(
+ return_value=httpx.Response(401, json={"message": "nope"})
+ )
+ with V1Api(api_key="test-key") as api, pytest.raises(httpx.HTTPStatusError):
+ api.search_articles(q="technology")
+
+
+def test_missing_api_key_omits_authorization() -> None:
+ route = respx.get("https://api.perigon.io/v1/articles/all").mock(
+ return_value=httpx.Response(200, json=sample_json(QuerySearchResult))
+ )
+ with V1Api() as api:
+ api.search_articles(q="technology")
+
+ assert "authorization" not in route.calls.last.request.headers
diff --git a/tests/integration/test_v1api.py b/tests/test_v1api.py
similarity index 76%
rename from tests/integration/test_v1api.py
rename to tests/test_v1api.py
index 8691827..0289bee 100644
--- a/tests/integration/test_v1api.py
+++ b/tests/test_v1api.py
@@ -1,29 +1,28 @@
import os
-from datetime import datetime, timedelta, timezone
+from collections.abc import Iterator
+from datetime import UTC, datetime, timedelta
import pytest
# ---- Perigon SDK imports ---- #
-from perigon import ApiClient, V1Api
+from perigon import V1Api
from perigon.models import ArticleSearchParams, SummaryBody
# ----------------------------------------------------------------------------
# Configuration & shared fixtures
# ----------------------------------------------------------------------------
-API_KEY = os.getenv("PERIGON_API_KEY")
-
-pytestmark = pytest.mark.skipif(
- API_KEY is None,
- reason="Environment variable PERIGON_API_KEY must be set to run integration tests.",
-)
+pytestmark = pytest.mark.integration
@pytest.fixture(scope="session")
-def api() -> V1Api:
+def api() -> Iterator[V1Api]:
"""Create a single V1Api client for the entire test session."""
- client = ApiClient(api_key=API_KEY)
- return V1Api(client)
+ api = V1Api(api_key=os.environ["PERIGON_API_KEY"])
+ try:
+ yield api
+ finally:
+ api.close()
# ----------------------------------------------------------------------------
@@ -32,10 +31,10 @@ def api() -> V1Api:
def _to_utc(dt: datetime) -> datetime:
- """Ensure the datetime is timezoneβaware and UTCβnormalised."""
+ """Ensure the datetime is timezone-aware and UTC-normalised."""
if dt.tzinfo is None:
- return dt.replace(tzinfo=timezone.utc)
- return dt.astimezone(timezone.utc)
+ return dt.replace(tzinfo=UTC)
+ return dt.astimezone(UTC)
# ----------------------------------------------------------------------------
@@ -43,8 +42,8 @@ def _to_utc(dt: datetime) -> datetime:
# ----------------------------------------------------------------------------
-def test_article_search_basic(api: V1Api):
- """`search_articles` should return a nonβempty set for a simple query."""
+def test_article_search_basic(api: V1Api) -> None:
+ """`search_articles` should return a non-empty set for a simple query."""
result = api.search_articles(q="technology", size=5)
assert result.articles
assert result.num_results > 0
@@ -52,9 +51,9 @@ def test_article_search_basic(api: V1Api):
assert article.title
-def test_article_search_date_filter(api: V1Api):
+def test_article_search_date_filter(api: V1Api) -> None:
"""Articles returned with a date filter should respect the range."""
- to_date = _to_utc(datetime.now(timezone.utc))
+ to_date = _to_utc(datetime.now(UTC))
from_date = to_date - timedelta(days=7)
result = api.search_articles(q="business", var_from=from_date, to=to_date, size=5)
@@ -64,12 +63,12 @@ def test_article_search_date_filter(api: V1Api):
pub = article.pub_date
if not pub:
continue
- # Perigon returns ISOβ8601 strings (e.g. "2025-04-28T12:34:56Z")
+ # Perigon returns ISO-8601 strings (e.g. "2025-04-28T12:34:56Z")
pub_dt = _to_utc(datetime.fromisoformat(pub.replace("Z", "+00:00")))
assert from_date <= pub_dt <= to_date
-def test_article_search_source_filter(api: V1Api):
+def test_article_search_source_filter(api: V1Api) -> None:
"""Filtering by `source` should limit results to that domain."""
domain = "nytimes.com"
result = api.search_articles(source=[domain], size=5)
@@ -85,7 +84,7 @@ def test_article_search_source_filter(api: V1Api):
# ----------------------------------------------------------------------------
-def test_company_search(api: V1Api):
+def test_company_search(api: V1Api) -> None:
result = api.search_companies(name="Apple", size=5)
results = result.results
assert results
@@ -98,7 +97,7 @@ def test_company_search(api: V1Api):
# ----------------------------------------------------------------------------
-def test_get_journalist_by_id(api: V1Api):
+def test_get_journalist_by_id(api: V1Api) -> None:
search = api.search_journalists(name="Kevin", size=1)
results = search.results
@@ -106,7 +105,7 @@ def test_get_journalist_by_id(api: V1Api):
pytest.skip("No journalists found for the name 'Kevin'.")
jrn_id = results[0].id
- print(jrn_id)
+ assert jrn_id is not None
journalist = api.get_journalist_by_id(id=jrn_id)
assert journalist.id == jrn_id
@@ -117,7 +116,7 @@ def test_get_journalist_by_id(api: V1Api):
# ----------------------------------------------------------------------------
-def test_story_search(api: V1Api):
+def test_story_search(api: V1Api) -> None:
result = api.search_stories(q="climate change", size=5)
data = result.results
@@ -131,7 +130,7 @@ def test_story_search(api: V1Api):
# ----------------------------------------------------------------------------
-def test_vector_search(api: V1Api):
+def test_vector_search(api: V1Api) -> None:
params = ArticleSearchParams(
prompt="Latest advancements in artificial intelligence", size=5
)
@@ -144,9 +143,9 @@ def test_vector_search(api: V1Api):
# ----------------------------------------------------------------------------
-def test_summarizer(api: V1Api):
- # Uses gpt-4o-mini by default which is more stable and widely supported
- summary_body = SummaryBody(model="gpt-4o-mini")
+def test_summarizer(api: V1Api) -> None:
+ # Model is one of gpt-6-luna, gpt-6.1-sol, openai/gpt-oss-120b.
+ summary_body = SummaryBody(model="gpt-6-luna")
result = api.search_summarizer(
summary_body=summary_body,
q="AI",
@@ -159,6 +158,6 @@ def test_summarizer(api: V1Api):
# ----------------------------------------------------------------------------
-def test_topics(api: V1Api):
+def test_topics(api: V1Api) -> None:
result = api.search_topics(size=10)
assert len(result.data) > 0
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..d0c1d4e
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,872 @@
+version = 1
+revision = 5
+requires-python = ">=3.11"
+resolution-markers = [
+ "python_full_version >= '3.15'",
+ "python_full_version < '3.15'",
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a1/98/7e457a3b014976887a050561955c0b76e1e7e2d6e27b78ecef2ef80518f2/anyio-4.16.0.tar.gz", hash = "sha256:59bb03251907983d1603d27c3087ddb1e93299ea2163adcdba37899a4fb12ffb", size = 310971, upload-time = "2026-11-01T11:20:07.282Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/16/7757fb84c819accc7d7e9af5291b261e4abb8627ee040f99e7d35ef2457a/anyio-4.16.0-py3-none-any.whl", hash = "sha256:c66ae634e0b8228396b801741beb3294326d4b6102f7bba4041c0dfb58c29026", size = 134521, upload-time = "2026-11-01T11:20:05.59Z" },
+]
+
+[[package]]
+name = "ast-serialize"
+version = "0.13.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/d5/5dc1ee8a80c3b20a0b63c61b43ba6bc53517faecd1def47bcd752d22268e/ast_serialize-0.13.1.tar.gz", hash = "sha256:401cc84e8888d4d76f2c8d296fa006ed1a5ff993b6aff90dd777c03973efd025", size = 955137, upload-time = "2026-10-17T00:04:17.183Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f3/a8/fbd136b9f70c59e75f59a8e974c84102976f1201828b4f886480ba956936/ast_serialize-0.13.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3e129d44b0cffbea24a7f40117ce49e30993c3d119e584d4817ebd6f87246456", size = 897306, upload-time = "2026-10-17T00:02:52.479Z" },
+ { url = "https://files.pythonhosted.org/packages/50/3f/2ad51e9823a790e7a70fd781e9595ab3abeace27f3aefcaa1939457efd72/ast_serialize-0.13.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:523d45bab16ddaf1399c04c4fe81bbf180f81e33c2f7bcbe76be56b0f7311ab4", size = 1236082, upload-time = "2026-10-17T00:02:54.82Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/5c/8e959db5cc2237f9d4c560a8e2253c98f9746883fb4774651e4cda458901/ast_serialize-0.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:638333fb8ae72c62753f9e794fda20290883ddc65b0d85e26e08f8bb227567ad", size = 1221242, upload-time = "2026-10-17T00:02:56.395Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/05/ab1c56c3b7adcac88764bba609502d6b4989e449b2dd60eb478e1d20e369/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc34c670747d85d4232834665e247009413eaebe47c69eaf60a5c439aaecaed9", size = 1282105, upload-time = "2026-10-17T00:02:58.275Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/f4/41981fcd2ddffe0760e5326be9dcbd2eda9478a8430a637e5123bd8fcb26/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c8ebd419811c435812d242fb2a173e57dfede198c9ac9a0a584666ebc9b9331", size = 1287905, upload-time = "2026-10-17T00:03:00.068Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/43/b783addc5681153707121991b7b47892f7e9c8ba58a0d03b71a3b023ba47/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8899c038b1c15ae8b98e379057fb3ef398ceb0b21d1737a8156dd5a0923b7e51", size = 1541806, upload-time = "2026-10-17T00:03:01.503Z" },
+ { url = "https://files.pythonhosted.org/packages/47/10/969db095038ee15270f65ad20d1129c5e4da605bf7e93b70ae640f1c8a9f/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e14c4de98f965647a74e6eaf37e20207124e45451ccd36e84a6f6ebab45e0372", size = 1309946, upload-time = "2026-10-17T00:03:02.971Z" },
+ { url = "https://files.pythonhosted.org/packages/06/dd/7c623d2fb0b868948952d1787e629a43b866f7d4caefd5445e20aefacec4/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3e8a3eff69af640db985360fef35073fb46956dfc3e5c614cf00920d2746dd", size = 1302773, upload-time = "2026-10-17T00:03:04.378Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/c2/4d49469110a19a44fc6269f4fd59679bcf84df068f185f89b73bee4da19b/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:75d7750fe98f6f35a96c43aba8d98a2e623e22c778a0c17ab9ede92eb10f5d82", size = 1299579, upload-time = "2026-10-17T00:03:05.88Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/9a/2d381f1f262dda15affc8ad643fba1619eb07e7d9f8bd904deffef183006/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e8ddd63e80fc280c2f790157386f27f9b9f6ccb9c4f8e803b863cbfb085769", size = 1355969, upload-time = "2026-10-17T00:03:07.321Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/9c/2266794e6c25fa9ce51ce04a9bf4b7e330e3ac9351ba3fa14e487678acdb/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9221e09496fb942901993c1812ee87950cc0ecbb514c7c0f1f5fa6d78f701ed8", size = 1460227, upload-time = "2026-10-17T00:03:08.733Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/d6/14deb28e5d8f328f626a0778ea2cf98fe908f2bbcccd804cb6f449ee1782/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7a5c38bdb93669c596f94fb7204dd6ebe4fadb19058ac9a2faf2948c2a175f96", size = 1562691, upload-time = "2026-10-17T00:03:10.25Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/19/7a28c0e5ba54f0d3e054f847a2a549b748cb179ab433321a8dc1b39e398d/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:acf4e9f055d75058ab01536da330648746f2f82b7697a85230547f2d5ebe895c", size = 1558669, upload-time = "2026-10-17T00:03:11.799Z" },
+ { url = "https://files.pythonhosted.org/packages/18/9a/f9e2690eab58217f1d395a964a78efa7eaf813a1e38be8dfd9a8d45d0840/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87427913c325adc811e81e7c9a757b061f47ef1e29114ac479aa0953a2687a98", size = 1665071, upload-time = "2026-10-17T00:03:13.284Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/f3/ef5b159a0d7161b404f05e7ec21cf7735ee7edc10c000cd2cbbc7bb6b69a/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:ad88921f95b92bc144d9ad059a0e16f8c1ae69f5239f2d3c6536fd2774f73dfe", size = 1473364, upload-time = "2026-10-17T00:03:14.563Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/87/6e8ac4a865124a12cc4f915bf1bcfac3bba426cd5f58d3712a873ee61012/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f8b17e2921e56621bcb46792bd3e0b82ca3b85de78d5936e7b8174ba1758b0aa", size = 1500566, upload-time = "2026-10-17T00:03:15.917Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/1e/29155f1621433008013ad5c940a03d41a676be0680c41527040472d37583/ast_serialize-0.13.1-cp314-cp314t-win32.whl", hash = "sha256:2f9a9ce8b0994f757b941f7f7fe384a6666b4162b941ebc5c248081baea18e47", size = 1128674, upload-time = "2026-10-17T00:03:17.481Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/64/2506da65143ea138de5822e9173e0b72320a2f70e23bd723f044033175f1/ast_serialize-0.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5d99910e6b9a9b9360bcca2ae99aee36cc912ce058df2a57d1c33099b6cb2593", size = 1163306, upload-time = "2026-10-17T00:03:19.046Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/8f/c7cb22638899b0cf15908809c270560e46395e65604bc59e4b623ab9f405/ast_serialize-0.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:a789cbe14d0a0e9ec9b32a456e5abc81ad8799479c3cfd88a29e3442262aebe6", size = 1133775, upload-time = "2026-10-17T00:03:20.377Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/e2/b0c140fe50fa39dd249e382f7733af6f816c90021fe7959a0b026f86237f/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:23d768369b89d551077dc173da6892fad00443338417523b6732f4e8ba4a7ed5", size = 1236160, upload-time = "2026-10-17T00:03:21.945Z" },
+ { url = "https://files.pythonhosted.org/packages/23/c4/a8a2ca21e3943555b48221f8fba546877a7ad39efaf6505506524967d7c4/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:68f8829d114dab6c9db4a8bbb8d0e342958faee1b5e15a29022678df639960f8", size = 1222214, upload-time = "2026-10-17T00:03:23.462Z" },
+ { url = "https://files.pythonhosted.org/packages/15/19/2f58f2ba64e4799b39de85c140aef09a03aa6ea53be75dd292a9d9b9d8ae/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e385b17d5bde892a6184003a9aff3518387908b73f8f5ea539313c07c8c0ede", size = 1282979, upload-time = "2026-10-17T00:03:24.695Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/af/ca6087fe84210c1ccd43e3182917bb70649fa1e69745a90c1d59ba08fea8/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02686b281ec9b8e9614f6f0db27e12e4cac515f336c5d05b678bc9eb73ff2af5", size = 1288596, upload-time = "2026-10-17T00:03:26.04Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/2c/4ef964c763329a0347125e745ec0451ba4fbaceec9efe2ac729b7cd55f17/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8ab2c2585b2e22e48cd4aff2136fce4eab4ced569b85f150ef9a5a5d6f34d49", size = 1545540, upload-time = "2026-10-17T00:03:27.52Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/85/aa961dbb75813cfa94de0ce9a16a9510c226b95f604c405421e727f44bb0/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c652d9f5d52d8b81eff3ff2c95c9dd0f281de45f6ad5d3fa95527550c396454a", size = 1309374, upload-time = "2026-10-17T00:03:29.091Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/e6/f2b67c327d9f9533e41791804403ccc2dc457821195a2e51f0b587a90b90/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121e1d08d58b75c15491cc3003609cbad71ac2b0016043c36d30a6b2032b6605", size = 1303466, upload-time = "2026-10-17T00:03:30.451Z" },
+ { url = "https://files.pythonhosted.org/packages/73/1d/33f18b81d1bb1f5d682943ea92fda8130a6593627f12f6201668d40824fd/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:30cf89b6a6844542d116c758725d13de5b97b9410a51ee577715f761270d0c1c", size = 1300576, upload-time = "2026-10-17T00:03:32.102Z" },
+ { url = "https://files.pythonhosted.org/packages/25/cb/64914144925f1a4abdd9217e717b122255c38fc5ebe4342f92052f3bacbc/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:732aaae9e498c973fc63fd7f308009b57c4afa6ff59d0b674d894e57d0bca01a", size = 1355960, upload-time = "2026-10-17T00:03:33.676Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/80/bd29379fab2bce19adc0bf9177002b11b23dfe8d8fecf409998b458b4624/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:d9c1a920b2aa0c1e016bed7721689717034ca0dba64ac3a3ff243b003d4e8dd8", size = 1459779, upload-time = "2026-10-17T00:03:35.09Z" },
+ { url = "https://files.pythonhosted.org/packages/83/89/012dbe733de05ffdf8e40ca0f9fa5d417cb16ec93a74463c57c001914198/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:847cad4c22953823f5e76d0b126c6f9da670cfa45b1df41b9324e22189d77611", size = 1563399, upload-time = "2026-10-17T00:03:37.285Z" },
+ { url = "https://files.pythonhosted.org/packages/69/37/ee4bcb0358bee38744eb0f18560446572d095fe3bf6b63d6f739d08a839c/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:aca07de3ca8e3d9f2b716c3c31f4f384fe4bd2d03e09912417cff7a3e08036f7", size = 1558808, upload-time = "2026-10-17T00:03:38.711Z" },
+ { url = "https://files.pythonhosted.org/packages/54/1c/ff44c5355251d61ef22934c4430bb31a61de4781d25a992154aac9b8c82a/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:5db12d843d931b9d1f1519ce66b9cbfe72d8563b75c1204d255322254d1964dd", size = 1666324, upload-time = "2026-10-17T00:03:40.376Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/bb/6da4bfc8aa3403e6447607131bf3463acaf954d990c6c8b6787c1902ddfe/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:fb8021f4dc3296de18737e3b8b34994411da725374d74a0b9eb32a738f2a23d0", size = 1474089, upload-time = "2026-10-17T00:03:41.715Z" },
+ { url = "https://files.pythonhosted.org/packages/21/97/897e33bef2eefd880e6350e375cc66bc5bcd00a139be480fd655f5b3f095/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:0d764913374ba21df8cd618045b371ed104ce55c6276de7c59e554a4f5ba26fd", size = 1501542, upload-time = "2026-10-17T00:03:43.08Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/4c/821211ac83870c27e4412381b7c7838336456b40575bf840e4680b2018d9/ast_serialize-0.13.1-cp315-abi3.abi3t-win32.whl", hash = "sha256:2bad302ae2427454ef9dab71bd5c3ad67188860c39d36daee38f72031edb39c4", size = 1126118, upload-time = "2026-10-17T00:03:44.616Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/86/2b2d5e6c4fa352874a80618e742ffd32d249dee6c119f4233b51afc6b8f4/ast_serialize-0.13.1-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:032b09d86c785c3984e975378b7861f2a68ac44e22ba7b07b93a536b2524eeb8", size = 1163766, upload-time = "2026-10-17T00:03:45.986Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/d610cfcdabaa44fd62a54ca1fc57a7dba120643798b089a14914973562de/ast_serialize-0.13.1-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:929eda0c419e622ab49df3a4c45f81c57dcc10874bbad55d0470a4a03324dac3", size = 1134043, upload-time = "2026-10-17T00:03:48.453Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/89/0e30f9eddf7d44ccf9d25979030754fd0457af61fa4279acb8c293a04f97/ast_serialize-0.13.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bf53a3ba1cbe45399fc1e4ccd684b8f2a11cca450f89706b79122f478534c6f5", size = 897546, upload-time = "2026-10-17T00:03:50.093Z" },
+ { url = "https://files.pythonhosted.org/packages/81/92/d705be88ff452b9ba0088b554a0cbd308fd01879da092b33fd2ad7d1aa60/ast_serialize-0.13.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4bcf1d5f4eefecaa325a34ecf079b2741189e7d0821523f92cb343a389a67489", size = 1242416, upload-time = "2026-10-17T00:03:51.521Z" },
+ { url = "https://files.pythonhosted.org/packages/60/c2/98370845a7f35cb8a25782e78284b48e01bf3128339881d48d613f304f8f/ast_serialize-0.13.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1b158f1c9e3a0d386913924c57995d6df78224868a4d0f215bdd41587c97b34a", size = 1229729, upload-time = "2026-10-17T00:03:52.825Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/8b/2d7ffed76addd9d3fcf37fb171400942735c8912264c763561a85407f6a8/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a6201b69ce42e919f2fd9a559138d5410b5d8afff7c3a6b1f500b0481ff7d8", size = 1292255, upload-time = "2026-10-17T00:03:54.148Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/2a/bb9e0e719d89cc950fadfcfcc9cb4bc7640419c4228c852f031ab8abf440/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eb0be64c9b8f1ed26637eef345abe75ea90351c92c85a8aec7838b3af971011", size = 1298501, upload-time = "2026-10-17T00:03:55.559Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/32/13ce07dce48226f5bd9cfd8f708ec101a8be05aa430cda4918ce31ce8c82/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2427bddfca208e84de700044fadf9aef2769a70bae740a1bdabb89ee9e1117", size = 1553758, upload-time = "2026-10-17T00:03:56.952Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/fb/9b51d186db0c20ca28cbaaefd50f03b573c89327b9a2627820cbce22196b/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88df960e67184a71874e5b2ee014f87d1254a0dbc1f903c7bd7b1ab7733ecaef", size = 1318258, upload-time = "2026-10-17T00:03:58.418Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/fa/da311559dd3dae9af5825cf21660775a929b970f72e3786af3f2cd667944/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f99786e0d62082a5b121b3437f3167562133711df22128de1e9886f57f0b226", size = 1313260, upload-time = "2026-10-17T00:04:00.194Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/11/97a4b9e5b4776d72f47fcd9a09a497bbf68ec8c1ae074307a2c51e410d85/ast_serialize-0.13.1-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:a01b515b25a03184eff4acef25b981ae007916379682e908fc8785b342a0bf3e", size = 1309992, upload-time = "2026-10-17T00:04:01.575Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/c4/62f85121a80cd451ef04f550cf0f7ea0ad9ff46cc4e9a5b9ae439b6527b1/ast_serialize-0.13.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:29422d7bcd9e7f39411571c36cec45ce8a7a9b3ad4b2e7aa64497c15a9e397f6", size = 1362715, upload-time = "2026-10-17T00:04:02.881Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/fa/c5f4a545f72a7681e3295bab6e3600ee0862ec4391bed961fe9f735b67e0/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ffb0f548a7dc126067fdfb7b2325ab5dfcdeecc29faf5e0f0461c96d6ebfa3d", size = 1469931, upload-time = "2026-10-17T00:04:04.181Z" },
+ { url = "https://files.pythonhosted.org/packages/38/a9/65aed4f8fa7a001a7cfdc03e68e844517ab041ba6fb614b7d78a1d6ac056/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2876444a5f87a7c76f371af3c07c2fda563a08bc8af52568df812e8aba4bf568", size = 1570945, upload-time = "2026-10-17T00:04:05.778Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/8c/44b2da991cede79bfbf52089f33a5f0168945466b3d9cd38b5dcf08e7130/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:885a62ec6481ce58ab24c6357360d7a82023b62dd7e5e7ceccf59e9662a3272b", size = 1569978, upload-time = "2026-10-17T00:04:07.347Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e4/109b2a5beca700237767d5add920c2f8e1ba9bd5799b0faf143d0e9cf4ef/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:2015fd0165e463aac3b1aadc6a1fa1e5820c14ca950821f4fe02f8a3f6a388d6", size = 1676082, upload-time = "2026-10-17T00:04:08.7Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e0/7df70f6dbb11b5762da5416318f08316a1c51348e0d69ec79ca5f98f836a/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:eb41e1906502ac80d24ff343f45008552a3f5b5158607959fe6a07dbfc6eb037", size = 1483207, upload-time = "2026-10-17T00:04:10.162Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/bc/523aa1c08bd69b5654621beb3dc1a9035715a4a63762fb8d219d6fb3eb81/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca9acc67f796aa2554fa3037dccc022a4b08b38b2ad153edfac9c9bb7cb1f3c6", size = 1512140, upload-time = "2026-10-17T00:04:11.589Z" },
+ { url = "https://files.pythonhosted.org/packages/66/83/6607060659998f00afdea0d8375c1e6e2b7d515ab905090d9a5a02f48851/ast_serialize-0.13.1-cp39-abi3-win32.whl", hash = "sha256:4f98da562e5901d69c7dd30885594c5438f859edf354e9ac7517d188fb026c3a", size = 1131889, upload-time = "2026-10-17T00:04:12.953Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/6f/bafd6490fe7b355627fabe95f4417e3d02da93102c8552550eb9a0774e1e/ast_serialize-0.13.1-cp39-abi3-win_amd64.whl", hash = "sha256:ddf7cbc9c8abf70102c4a088efe154bb86bfb825b0a10e556d2b15972cf99bd7", size = 1171153, upload-time = "2026-10-17T00:04:14.362Z" },
+ { url = "https://files.pythonhosted.org/packages/11/25/4235f4b375a67a37ac595bf478a5407a744eaf7b64113c1c3a5432b8d954/ast_serialize-0.13.1-cp39-abi3-win_arm64.whl", hash = "sha256:48f11d96c9ec71119612f8e79a8c55c12f2b136716d68347433c561d27a25f43", size = 1139035, upload-time = "2026-10-17T00:04:15.767Z" },
+]
+
+[[package]]
+name = "bracex"
+version = "3.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4d/fc/137095eb2f24ccf0b4db196abd9f5cfe74398a834af51d4bc4876e0beaf0/bracex-3.2.1.tar.gz", hash = "sha256:07a9b2100ceecc0c24afa1a1714bf8d4a6abb43d592939455f662b682ac9baa4", size = 32694, upload-time = "2026-11-03T14:13:43.53Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/ae/30dd8b027b4032b27e81364de5b74bfbef50b3c84c6aa0ead351bbb3749c/bracex-3.2.1-py3-none-any.whl", hash = "sha256:e8622d46b60ef6062b4091033f97a304e1b205e70c0285743c5038b73886a47e", size = 12527, upload-time = "2026-11-03T14:13:42.032Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.10.23"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.17.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/78/4a/740c3134227763ebc30a226d7f2f29b25e35898d162dd8b2dd792be83fb2/coverage-7.17.2.tar.gz", hash = "sha256:d05a1bef52f4d7f1b484a1cf46a8929e50963f910e51396373f06cd3b6248f38", size = 996756, upload-time = "2026-11-04T19:52:34.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5e/9d/e3db9071ebeda48e47e5be0fd0404219954e7888f64e7c37df324a79dc3a/coverage-7.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3f49ac9c9e5fd4b450677eb44f4b14ea9ab71950b2896888c9f6a22d5cd1773", size = 227732, upload-time = "2026-11-04T19:49:22.217Z" },
+ { url = "https://files.pythonhosted.org/packages/49/a1/260786d936940102052a51cb166f80f4a98e67fb6142aa050f1af48a2385/coverage-7.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d3d9d79a2b6cdcdde5a1ab1ae89953faea5cb28c0eea17a413ad997bb3cc8a1", size = 228154, upload-time = "2026-11-04T19:49:25.066Z" },
+ { url = "https://files.pythonhosted.org/packages/be/80/f5176b7f6aa9e05e37e1bb5eae473c5d015485c36206934f53b2b2795076/coverage-7.17.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b71aeaa4440cbece5498acda30838685793d3bb960aff87ec97fbb546715a5f", size = 260181, upload-time = "2026-11-04T19:49:26.523Z" },
+ { url = "https://files.pythonhosted.org/packages/03/9f/4669ca11ce1932cc18fce766ead327ceb41a92055608c179b137067e2805/coverage-7.17.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:01303d9e68b6e6bdc5459b54e3bd0e3539c94c25b50ac7362b7cbcacbc815fbc", size = 262404, upload-time = "2026-11-04T19:49:28.113Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/99/efefaa02a4b2876ffa4a389c81a8074c888442b12c64046b448718fa5590/coverage-7.17.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1947bd9d82e405c892c50b0d8dc0c1dea5df2a962129ca238c500a6bcf887509", size = 264497, upload-time = "2026-11-04T19:49:29.773Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2f/a565096e5dcf8c511332d397d712b056f06091ab3efc10adb5a4d22b0cc2/coverage-7.17.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cd1a7472a972fed01c89589b1560846e32e942f8329a515c4059d4521c6579a", size = 266507, upload-time = "2026-11-04T19:49:31.507Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/09/87f9b02f97179f2c76fa5724e0ce00b9db2bbe16b7e21d89c8fc1f3a77d9/coverage-7.17.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b7fe217808239437b8509b869fde2e0cac48776a68c626bd6b15411b087e3de", size = 260297, upload-time = "2026-11-04T19:49:33.002Z" },
+ { url = "https://files.pythonhosted.org/packages/db/12/70733b58d508bc80dc983408b06f1fd0c59fde0eb1379dd9473b3e290856/coverage-7.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:565f1f1a9632d82678525d668a10fe070a285603a9d3581505b0e1a66624ad50", size = 262196, upload-time = "2026-11-04T19:49:34.412Z" },
+ { url = "https://files.pythonhosted.org/packages/33/de/0495b108a3091f2389800455b615939867a3ac19f24f0fc186f4d066b4f3/coverage-7.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:189cdffb3ebc53d6133de67952dac7cc88d1fcaff5480623fb00ef0bf1cef918", size = 260212, upload-time = "2026-11-04T19:49:38.462Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/ff/39cda949e0e9bbc16620d8b843fd78929a46edda2566e54b9597bda61625/coverage-7.17.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f25209696631752468a25e5954fe119503468b3b29b6fa2c621ae766153c6f50", size = 264136, upload-time = "2026-11-04T19:49:40.266Z" },
+ { url = "https://files.pythonhosted.org/packages/85/e5/0eb1bbfc80e1a03318d81fc044dc1c4fc075921f7f618298d42837dafa66/coverage-7.17.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:20faaaf3a49601afbcd6d46c17a594b0ae62631498def44e5eed94789962ea18", size = 259738, upload-time = "2026-11-04T19:49:41.79Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/18/098ea8ee450922eec7ab48a35b4a7fd6a49c4a97416d1e4cf66092c24dc4/coverage-7.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7c123b6bbc183a14372036769f033098026aa75fc8fba37d028b0b772356e90b", size = 260937, upload-time = "2026-11-04T19:49:43.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/1c/a6ee041c4bc8b6ec7bc9f417daf89e3ee702182a48d573267ccaaed17c64/coverage-7.17.2-cp311-cp311-win32.whl", hash = "sha256:1c3146ef9d14c011517422489784a7ac344477e9cdacb05d9529e912721cd5d8", size = 229599, upload-time = "2026-11-04T19:49:45.014Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/df/7f077fb2e71b1d73fe8285be7c6ba43dc085f9eaa2384d4e8e55560073a3/coverage-7.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:51c6a60e63ea993113ea5cd24a401cc2faddc54aa7acfddaffa5d3b8c0161b9d", size = 230202, upload-time = "2026-11-04T19:49:46.548Z" },
+ { url = "https://files.pythonhosted.org/packages/90/56/89dcd5907d0cc04b07746e62e1cb896c0f694e5af40aa0c980458825c6c5/coverage-7.17.2-cp311-cp311-win_arm64.whl", hash = "sha256:b08ee3ef53e80e5a50c68b3d9764363e373acba540e1b58daee0a9a7f23fdfb2", size = 230110, upload-time = "2026-11-04T19:49:48.005Z" },
+ { url = "https://files.pythonhosted.org/packages/22/6a/a51808ac625c457326fb37b174d35e1badb1324c6def5d9668089c4b14ef/coverage-7.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:289a24443352713745f79c1b6aae4e99ced2f5ff3e86bb630c33c631d50f217c", size = 227838, upload-time = "2026-11-04T19:49:49.546Z" },
+ { url = "https://files.pythonhosted.org/packages/31/24/f32ec1a5ef947294252e77c5d40ac0903ba1de4c92638edfcca8629c067f/coverage-7.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da4b0aef427eb96e422b444e5c2c3d38768fedb9141346294c38717a8b15b416", size = 228060, upload-time = "2026-11-04T19:49:51.221Z" },
+ { url = "https://files.pythonhosted.org/packages/01/8e/3097367327c69e65521b9d9bf8b9d2ddecefa8c21bd08e17bc068bd3db93/coverage-7.17.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90c6c5f93606b8803bd7f8a0b2ae05df7e35da4504a1c53cfe9294a1731e1f49", size = 260359, upload-time = "2026-11-04T19:49:52.756Z" },
+ { url = "https://files.pythonhosted.org/packages/73/65/774fc4ec86807b6ee66b46465bc49b34bb6d291e9bff6ddfa9708ccb4053/coverage-7.17.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:36314997533266bde3d07765289674304d61bfe49165bb1b4fa9be12cb4ba0d1", size = 263687, upload-time = "2026-11-04T19:49:54.351Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/72/17b5df5d8211e1880169b93197ba98b21b30ccf8f116284536113b7582c5/coverage-7.17.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1682d98ded28524bc6853cc4e7c65175b3e917f38b2e2ddebb28cafd8ab4de74", size = 264220, upload-time = "2026-11-04T19:49:56.172Z" },
+ { url = "https://files.pythonhosted.org/packages/de/0c/603ba7fff1aa4c8064fdf4a1fff6d9fb51c72a5828429dfe858be48e38b3/coverage-7.17.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8eab0a5cff8dbc563d1416a6e079ba3c146712e47167c022a5df5ea01bd76df9", size = 266498, upload-time = "2026-11-04T19:49:57.718Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/ca/f2ae6e47e18b35a0dbddb9d8f51be34a6d66b41120f2e0f7363ccc0812f4/coverage-7.17.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:635337a96895f2ffcfe2aa7554468a988f468fb67a8007c1a65e95148c1855a9", size = 260591, upload-time = "2026-11-04T19:49:59.159Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/4b/b09540a46395a6a1052ad114bf366e383c64e223374f4911a85620667930/coverage-7.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79cd2d9742419b6d1aaeb1f3fb77a8e6a01c09ebe5bd1b27011752410a5e3781", size = 262107, upload-time = "2026-11-04T19:50:00.607Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/c4/42783cb43481fafafffbc7be7fe6026258ffd30a575707733109c9ef0679/coverage-7.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b0209fff1ca92f1e2e344d54b5b3def74efbdb46ba807cd81976a5e9e569cac3", size = 260271, upload-time = "2026-11-04T19:50:02.06Z" },
+ { url = "https://files.pythonhosted.org/packages/37/07/192ee81dd1ec4da58ae0f03fdd0045d2b821007e4194f7704d6e495733f0/coverage-7.17.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a3984fb8c2407721f9bc05573fc9a770752efc756d55068ccbde1fe53e42233", size = 264320, upload-time = "2026-11-04T19:50:03.613Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/94/8d983310d98299a19ba23efbb324492f4bf8d8668d610eacd304e451f138/coverage-7.17.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:698ca1575135215be25ac9c5d14f0b5455135d29602b7ee81e511d64a0435c85", size = 259774, upload-time = "2026-11-04T19:50:05.363Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/85/6c923d71c76566c6c65a2af104d39d69d44ae708a700de017e6c56fedfc6/coverage-7.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:686b6f8f9e57afc3a44096b17ee8de7e11e0a12f2da819e25047af1eb3b0fc6f", size = 262258, upload-time = "2026-11-04T19:50:06.807Z" },
+ { url = "https://files.pythonhosted.org/packages/54/f9/fd3c37b09891bfdfece486a6145345bf2b32332045a678afd293a7f2f05d/coverage-7.17.2-cp312-cp312-win32.whl", hash = "sha256:ee083412170118074bcc74361fcbc7104eb6d0b7c9c75eb3b84a4c324410c531", size = 229659, upload-time = "2026-11-04T19:50:08.184Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b8/4d395163c9bfae0e991abbd14dea9c9bbb4ade904e5f297cbfb7b8150216/coverage-7.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:e269dddf5ebac20dfcc5a2a459528a9d221ab828cb31dfdf19c7cfacdbcee673", size = 230240, upload-time = "2026-11-04T19:50:09.715Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/02/1024b8d4f60a9ecc0ee41a6000425caa63855d63e51da84c93c0b1dff894/coverage-7.17.2-cp312-cp312-win_arm64.whl", hash = "sha256:bac3c79c31f45274117ed64c65efeaed85665668aeba7d809c7c38801ada6ed4", size = 230043, upload-time = "2026-11-04T19:50:11.187Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/32/d750d7b10b0a9436349b3fb78491c5bb8f9b524f01ae6994d935d77d6ab8/coverage-7.17.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:882cade9b6dc43b2bc819bb444c0a13e4ed1f669a50e0de74adeec54fce486f3", size = 228978, upload-time = "2026-11-04T19:50:12.782Z" },
+ { url = "https://files.pythonhosted.org/packages/63/33/468f8c63aff5deef81bfb8e44771f68ebba328c0aa46c8a6ff27dedd0fda/coverage-7.17.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:f5a875e92f10c6463027d8151459f2d92b4c13527402ed1301eaddabd79d8cf5", size = 229048, upload-time = "2026-11-04T19:50:14.255Z" },
+ { url = "https://files.pythonhosted.org/packages/10/91/c8723e48c1497a3c56a3194973a416a6f0fb24560425685f960888e03b20/coverage-7.17.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e8da5e158d13556457f69245deacf6d8401a2c9c31afaddd18bea691e849b5fd", size = 227293, upload-time = "2026-11-04T19:50:15.857Z" },
+ { url = "https://files.pythonhosted.org/packages/61/4f/2f57b3dc6ef31e12a99e6dcb820c4ab04d973ea1466f73c856bf920a740c/coverage-7.17.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:42133a2647982db7968db138c0f4a57f5836101d8d504600a87ef8ebfa22a4cf", size = 227599, upload-time = "2026-11-04T19:50:17.418Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/fa/967e829238dda21c4bce17aef2f739187678c8e6e6fc880a8454945a7433/coverage-7.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e6c89d63d43738712ce1febddb7758ffeb56de08f1b2be68aeb9a9c7fee712c7", size = 227727, upload-time = "2026-11-04T19:50:18.924Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/df/803ba08e8937759dc3f18b93ece82f81b2fb446e3846e4d60a06a4e77647/coverage-7.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f94d465ee00ebe694ca866d66bbe45fa04618b1afcb377de934694f624fa7cf6", size = 227987, upload-time = "2026-11-04T19:50:20.867Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/12/92dcd9206e7bb817af42b8663cfeefdbb2c5a6c92cc7069d43e6892bbdeb/coverage-7.17.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7ce0acb27a823e8e5169282f538f0e182854cc8b472b88d45456f09b6fe364fd", size = 259154, upload-time = "2026-11-04T19:50:22.328Z" },
+ { url = "https://files.pythonhosted.org/packages/48/2d/6b028df8050caef4ce0df177686c92dba16ab7cf004040e5518f95b57021/coverage-7.17.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:70daa8e4b49541b51c0baeba0d802b0d14d3214daeebc071b3db0b0c7dc84646", size = 262065, upload-time = "2026-11-04T19:50:23.745Z" },
+ { url = "https://files.pythonhosted.org/packages/62/c3/429b500937ac446e36fcce517d040b0b8bd04a4bfa087bdf864175553fea/coverage-7.17.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95bd8dc5b356f78f10a967ec50f8902d56f24c623753ac9fce0aba9d6213d5c5", size = 262997, upload-time = "2026-11-04T19:50:25.223Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/91/263224a77de3ab1ee42d110b09f1ac9a02638794d3e002a859822638256a/coverage-7.17.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a5565a9dd715a5a94ffcf9a0ad4b6b1501b5441fd67d660e78742adff5d37a0a", size = 265062, upload-time = "2026-11-04T19:50:26.829Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/44/99b72f2961f4ba54ac14ccb016f3b212cc9a9dfda4c37e07a208efe01f9f/coverage-7.17.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:091eba4248d31b03828fc657c96d14adfaa8754f5aec7b73298b82894858db06", size = 259201, upload-time = "2026-11-04T19:50:28.386Z" },
+ { url = "https://files.pythonhosted.org/packages/be/a7/e97f63a7c345077e4e398d15e3aa5068edc28cb061f997f71fd0e32703e1/coverage-7.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2742952851c7f88b1484385f68c42c6388153520efb40ff09fc852c3783aa5c", size = 261155, upload-time = "2026-11-04T19:50:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/16/0c/73e470df7db83962e2cdd6c1a7ca7dc8f8632c17a6d378d6d9124ff8bc6b/coverage-7.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1da1999e3c371edd669b48a1029c86dc0474cdc6ff152d58cb3f15cb3337af72", size = 259077, upload-time = "2026-11-04T19:50:31.437Z" },
+ { url = "https://files.pythonhosted.org/packages/50/43/db8bc1b92d933b0b5f57d0e3377594c4f78ea31e84e8fbc22627fd17ecf4/coverage-7.17.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7804a6022fcd5e1d4de4dc676e985d6aea0119249e88c041e943f3cd07953527", size = 263172, upload-time = "2026-11-04T19:50:33.014Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f4/3e9e5cc9ccc911f8fb9595053da1ce58b9de8941b27e2f2f7058cdfc87f1/coverage-7.17.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6bdf3821fd0740b5369b9ab58b4e9d0d68a2c5c75a7ee0cd3beb624f9f15e4fa", size = 258926, upload-time = "2026-11-04T19:50:34.513Z" },
+ { url = "https://files.pythonhosted.org/packages/72/9f/0d67883bf55adf50424728937cbee54d76fa137817e6bb20f97061d01816/coverage-7.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09c37817768bb71fe3e70b6ce5e6ac1a0cd7398536c2f30a4460218c7b66e93", size = 261006, upload-time = "2026-11-04T19:50:35.956Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/22/71965edc14736073b55a40e521791d9233d5c3194fa6887a94f4431fbbcc/coverage-7.17.2-cp313-cp313-win32.whl", hash = "sha256:c10f2a2748dd084e44811bb9b26979763af9003115d9bf93c1da69011944719f", size = 229602, upload-time = "2026-11-04T19:50:37.564Z" },
+ { url = "https://files.pythonhosted.org/packages/47/0d/715cb37f51f908a6f6f33fe8c6fc95ab638b17cb014b1650daae0893c160/coverage-7.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:8dc07ed34bcb206f69d5b54fd3874f7bf39ac941c9f7aec2b53b29874fcfd77a", size = 230329, upload-time = "2026-11-04T19:50:38.988Z" },
+ { url = "https://files.pythonhosted.org/packages/39/f2/a5099bdc2a530f29c818a1c7e706669451c10e5f0eb62dcff76ef3568668/coverage-7.17.2-cp313-cp313-win_arm64.whl", hash = "sha256:bb74283661a239fdaf738ac0b07db4ed9b476c7532d3e713abf0f6a072781e60", size = 230055, upload-time = "2026-11-04T19:50:40.479Z" },
+ { url = "https://files.pythonhosted.org/packages/96/f9/30d6ff7a66e1519d79940fdee542ead9a63601de60be5cc9fe62c74bab58/coverage-7.17.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:0439f77deb5c023003533ae8909a9aa1e174c847d655327035b1e542c7c8a54e", size = 229094, upload-time = "2026-11-04T19:50:42.193Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/db/82c722c37dce98fe1abc953768903752bdd415dfe8cfeb7b744a7236a735/coverage-7.17.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e86a66f96d80c770daeed78394c1d4f6e8d03e5a3e08c5dab5275d56414f296a", size = 229213, upload-time = "2026-11-04T19:50:43.779Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/07/71b68d870975776cb185f11cd80e053b01739f3646488fe8b9721744f56a/coverage-7.17.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:87b30f3daa0bedda6b770b303410a6def007844772aaa143dc52675b3d95cd8a", size = 227374, upload-time = "2026-11-04T19:50:45.258Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/23/ed60f79576175e108c77df2bdc116f46e367aab108542ddeecaab752f0f6/coverage-7.17.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:3f219efbc3ec71c377e278dee201633e1d4ad0d2d9bdaddfd9489b2869d584c4", size = 227676, upload-time = "2026-11-04T19:50:47.095Z" },
+ { url = "https://files.pythonhosted.org/packages/10/7e/60a51850d3ff659b82cc211bc98fa126aab2ac9b81bd4ed6abc3e3720a09/coverage-7.17.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:36248fb6c03341501169539bcab35eb3a59159100be1246fad3c14cb73c17685", size = 227915, upload-time = "2026-11-04T19:50:48.581Z" },
+ { url = "https://files.pythonhosted.org/packages/77/80/a9539c15076fb9b95d8cbdb7f0caec90857d80bddca3eeca439f5e2e64e2/coverage-7.17.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2a6281011cf85699763bf88ab55e0774f6696f87b390f39152b14ecefadccb59", size = 228072, upload-time = "2026-11-04T19:50:50.073Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/3b/0a84b6032511834105fd4f8a8b4e76927783cea03dc4796032d5c4bfec8b/coverage-7.17.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3dfefb1ec16ca1a01e5da40ba162e268ced8bd3624b9eba4e9384ebfa14af7ec", size = 259991, upload-time = "2026-11-04T19:50:51.665Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e4/a3e6b8bcc219b00ea91e36f071c2659b253e5d6e1b258c31eb4524231718/coverage-7.17.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5c7a2da36804c6e6f10b368eae01ad56f5a4ac5ecce7297300a3d46fdef2a", size = 262995, upload-time = "2026-11-04T19:50:53.236Z" },
+ { url = "https://files.pythonhosted.org/packages/05/54/a666b65c5bb145d8796b7902ff1c0f60600a6c2a08949e20f2fb7af15de3/coverage-7.17.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6bf531deaebf8f69ba6015bba3fb817cab6694dfb69ac2a708889dc59a3926e", size = 263980, upload-time = "2026-11-04T19:50:55.366Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/73/ac9d06da9e0c143b874a68b17d8dfeedc45acdf1bdcee5b22a42bece098e/coverage-7.17.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:88d4dc83b6ad09a2134cad9d8ec780b274b17ac7dfba8c2c6e92e3bc468fe85d", size = 266457, upload-time = "2026-11-04T19:50:57.04Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b1/8a500e16cf787383b00b5a86eb50a490b4d8ebde505f92e1aaca515afc0a/coverage-7.17.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dc17707fd463b4dfab53792e41d50a23316ac6b98539007aa2388a1d44a8535", size = 259768, upload-time = "2026-11-04T19:50:58.508Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/be/642762c5973b89e2b8bf66c95778bdd80e96ceea442ae8fa753732597b73/coverage-7.17.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:df0a34d445eea64f3766e9ca2ff57bcde600a89c7c0330434a7a37e8d6e2549f", size = 261831, upload-time = "2026-11-04T19:51:00.25Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/b1/f7d6c0ef0a4aecf3f91f19df4ab3fa131fec8c89f554187511716584c9f5/coverage-7.17.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c5a98aff9eb681312ed403ecb7beb5f15fd25d6e0acc2554b9ca4b4d6f0b6705", size = 259871, upload-time = "2026-11-04T19:51:01.776Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/c0/3ae087d52cfa5119ad9ecbd193c40089924d1101cef7333f15a86dccb444/coverage-7.17.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7565c17092f2f4021abbe3f5007300c224f362364a388f5ddad9216dab44b611", size = 264169, upload-time = "2026-11-04T19:51:03.365Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/95/d324108991f892cb361ef8b2f91bd4cda9c2563e0954247409e9e6b911ab/coverage-7.17.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:555257578e20a46020c27b2af4ef99059cd906aa9b8e163baf9da9a5afb65a64", size = 259502, upload-time = "2026-11-04T19:51:04.91Z" },
+ { url = "https://files.pythonhosted.org/packages/14/b5/13a62eac66cecbaaa098bb753a3693b44454a4d21d53db4f1a0953f35f0b/coverage-7.17.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:460ba2139866e758c3a8e9d9422fdc889942bcbc5ccaf710dc4fbd8707d14aa8", size = 261613, upload-time = "2026-11-04T19:51:06.414Z" },
+ { url = "https://files.pythonhosted.org/packages/53/13/9ea31cfa72434449b9a98666760169d1e0ab31b3f6cdb12e68149106668e/coverage-7.17.2-cp314-cp314-win32.whl", hash = "sha256:e72cf9556f799c3fce7597c1bd7dbfc9104befbd08088deb3b9ae9fd98117c02", size = 229842, upload-time = "2026-11-04T19:51:08.61Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/cc/aa8f34b014979c973740f5a5ed6861ecfc6009082c70e72030ca1519e0f8/coverage-7.17.2-cp314-cp314-win_amd64.whl", hash = "sha256:205f9e329d9d5e47c320f8712326a01620689edae50531cc8934511e2c5e8f7e", size = 230474, upload-time = "2026-11-04T19:51:10.167Z" },
+ { url = "https://files.pythonhosted.org/packages/49/64/16afaa19b3886dc157c3c5ebace5241b84b96a005cb28545584e66b80895/coverage-7.17.2-cp314-cp314-win_arm64.whl", hash = "sha256:a5909dd087006c70c128297312ba67d877123e871100645d9bd73ae021a4d01d", size = 230325, upload-time = "2026-11-04T19:51:11.63Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/acc865e1411c8ebc5c5d230367f3bfed5c605ed54bb41b84fc58f4e7f137/coverage-7.17.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:674bcd023a3743666d4d7a15238e57a00c968553780a36c80d3779fc410b912c", size = 228384, upload-time = "2026-11-04T19:51:13.729Z" },
+ { url = "https://files.pythonhosted.org/packages/10/98/02c37c6a928ccfc84b7a43eda44d96d3b1ccbc93989c178754b7e67e7650/coverage-7.17.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ba45dc072bfbf1798e68ec81379b73473fc912174bff9f91924d77711a1d2b30", size = 228758, upload-time = "2026-11-04T19:51:15.394Z" },
+ { url = "https://files.pythonhosted.org/packages/28/ab/d503b0aaebb92b171260469463915af5e16a64457b289d4cb844e8c366ac/coverage-7.17.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d8a5dc709da9b90ad5cc859a9b88c9b801e65ca2fbf7cbdbed5b9cbf457fcc9", size = 268749, upload-time = "2026-11-04T19:51:17.179Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/e5/6d6ba15bde22fcccd07e14305be14291d041f732771a73f787297445d3e7/coverage-7.17.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:248873a144c067f981742ff4bbfb48b57e281a8cb7a3821d4e293bdb50b47970", size = 271301, upload-time = "2026-11-04T19:51:18.893Z" },
+ { url = "https://files.pythonhosted.org/packages/63/e7/bd6dcec5b8130f5bd8679087a2bc3a50a53c4ee78d5ec449610020088ff3/coverage-7.17.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0332e2b27fc5cc761d6b8a33118b958e6a46be7f00fd30e03a5cf103350c0b3", size = 273924, upload-time = "2026-11-04T19:51:20.605Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c1/b3f06aa1f1c51f4295fdfa6500eb7409aaa6273cf5f27bafc25217f3547d/coverage-7.17.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27fc4384a1154f7df30d304ccddc05ef457368c065670ecbd392606279b3329a", size = 275412, upload-time = "2026-11-04T19:51:22.09Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/f0/b68ab3654af3541488b26856e3043c13c782d8e6104fc8149746ecaa83f0/coverage-7.17.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:603140eeae3f9adc562f91b1b98d4d1fc491bd1f66c5082482c568d0b7891bb1", size = 268638, upload-time = "2026-11-04T19:51:23.653Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/16/4a9d56bd4ca4572cbd20f9843813623cef1b88b15f979ec4f381704d2f84/coverage-7.17.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:be0fec33b69c40f845c76f67fb4495b62a84da8c31a579dc6e18c5a63fa6a1f3", size = 271211, upload-time = "2026-11-04T19:51:25.678Z" },
+ { url = "https://files.pythonhosted.org/packages/78/d2/1c04b38bab92d2b7d62718d170b5bea43755fefee7e29cc056448c433d92/coverage-7.17.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:960e199965e506d78f62252355397a05c8cee8072ad2d82d932745517d566539", size = 268638, upload-time = "2026-11-04T19:51:27.262Z" },
+ { url = "https://files.pythonhosted.org/packages/26/c9/2c639e9aee78b0522cb3dc245de09685e34b479bcc45d4d2a277d0de1174/coverage-7.17.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d7a363c0788435ff5324d9582d37c63f215c75f974d755cfd6cfc9fbe0e64b04", size = 272935, upload-time = "2026-11-04T19:51:28.96Z" },
+ { url = "https://files.pythonhosted.org/packages/79/1f/11bdaf03c1b12f88094b57420b4238d3974ebec6a7d674b645a54e24f90c/coverage-7.17.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:afd81658f17b1a50bbc1d247a61fa435be0be471246136eb946f9cbfc437a4fa", size = 268057, upload-time = "2026-11-04T19:51:30.44Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/f6/43d40d1b4d4892e54c68a241cd5b92efed4d5934109e0e5e24a94759f732/coverage-7.17.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d88fcc24470d73ed4360e910f8549718b913ba56fb8775d9ef929a647835dfea", size = 269986, upload-time = "2026-11-04T19:51:31.913Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/33/6874185b791c71684c401fc74abd71bbdb102e8ee41c8f380cf25af41d29/coverage-7.17.2-cp314-cp314t-win32.whl", hash = "sha256:2c42c222939b8699e7bc0e453149627f211ebbbb2f74c1a04331b0cd9834e2c8", size = 230160, upload-time = "2026-11-04T19:51:33.304Z" },
+ { url = "https://files.pythonhosted.org/packages/16/e0/9057b4cf962ca6089b144f0d93a748f5e2d13876dd0f33f9d5a2c2ec49d2/coverage-7.17.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b94c509e26603ba7f8cb1bf8bb63967c7be533493be5668d8442cd06ec853d76", size = 231086, upload-time = "2026-11-04T19:51:35.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/4f/bd626297c942380279dff323e1d8a8937990d553119a4f887358488b1b54/coverage-7.17.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eca90e028932de597e2c50f454ad8cc631281e14d20b9f801a649ab555deb580", size = 230657, upload-time = "2026-11-04T19:51:36.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/92/ff8badc05ff0c4ed26ca62fb3d155e9014ff65768b76d5f399e63d9e9a79/coverage-7.17.2-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:cbf751ae86ef0d80e719915c087b05776c8d9300141f6c3e56f536d78a62c301", size = 229092, upload-time = "2026-11-04T19:51:38.222Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/8b/c5a5392e5a5dceb68f3b1b8cdba925c7997699f351d811cd4a41add9c29e/coverage-7.17.2-cp315-cp315-android_24_x86_64.whl", hash = "sha256:249c4b4eaac9ced6fb85e465027ca26348111c2760108fffc356a16be4f54cd9", size = 229212, upload-time = "2026-11-04T19:51:39.738Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/bc/9259a1fc49ad663861cbfb3df6a99d4c8f62a11b242a2db3358822d28b17/coverage-7.17.2-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e2fd4e664a34854cae8db58ca6132fb7b5a7efc67b14233e797194bd0d63b6b0", size = 227390, upload-time = "2026-11-04T19:51:41.16Z" },
+ { url = "https://files.pythonhosted.org/packages/20/ad/83f03da8ab846af246d4a47b651e5d90229a4c3314e31e8eecade63b378f/coverage-7.17.2-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:0712a43e39410aa567f2e85846490f8dd6c839820e7edef2142767c5c03162ba", size = 227690, upload-time = "2026-11-04T19:51:42.658Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/08/e110687f24245edbbe9c0986a6ccd04f1f9a6e90e329cbd4347e2b81d76e/coverage-7.17.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:298849ca0e385767096995cb57f72059b469f43f9927b8393f29d134f7bb1062", size = 227922, upload-time = "2026-11-04T19:51:44.376Z" },
+ { url = "https://files.pythonhosted.org/packages/93/24/7cfcd6adb4a0c18a10de12fd695edbf8cd121fdca0a6a63cea1bffc029fe/coverage-7.17.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:81a432793440edef268dbadd493393bd6f70fa4b87f76c58114f7f77f1ccfaea", size = 228094, upload-time = "2026-11-04T19:51:46.108Z" },
+ { url = "https://files.pythonhosted.org/packages/54/7a/af167a0bb6d0564209d35c8039b290327ecf7b3e34f838f9360b56d3a03c/coverage-7.17.2-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d14872ccef35972c4ac290f2432f43d0a7bb230cae2b5ed26871ab86221e8b5f", size = 259940, upload-time = "2026-11-04T19:51:47.599Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/c6/4f3931c3ff80a6f2608209181adb00527970a4b43b64c3aa323383c37d51/coverage-7.17.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3bd065e75c077d6de8cd3fe68699a8b47be3ec595af6b66e3629926839094609", size = 263632, upload-time = "2026-11-04T19:51:49.047Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/71/31a4945459008952061147ea9b85853902507d865a7272a9f64be0c4a093/coverage-7.17.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d3626b9816f7eadd0993c3703a1d8ed124b579cebe5201b446f1c0d551fa245", size = 264713, upload-time = "2026-11-04T19:51:50.569Z" },
+ { url = "https://files.pythonhosted.org/packages/82/6b/a4eb258b0373034f8f19aeea4c1bc4c89942b5b228812ee9952f033dc611/coverage-7.17.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9639c2ea019c948b52067a0f74485039eb84b7a2de828ba6d178e663fb2b8bd", size = 267091, upload-time = "2026-11-04T19:51:52.09Z" },
+ { url = "https://files.pythonhosted.org/packages/02/ae/9410f2f4bbaa698ff3869c7c09e0fa10d74482a1ec8040063157612f07a0/coverage-7.17.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:19d508b0611486f8e23e28abcba77795ee865ff0e0cd936349ba692c8d0d8ca4", size = 260326, upload-time = "2026-11-04T19:51:53.508Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/ee/97549ec1bdf6ff6d17612dbcf4b2205daa74510273ac1efd1460e3db275c/coverage-7.17.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cfe64d2501806a04507217852a3132a9a9076321435500bf3b11943a5c872732", size = 262595, upload-time = "2026-11-04T19:51:55.514Z" },
+ { url = "https://files.pythonhosted.org/packages/36/72/76e04605522a6dd81bbf43e1b407ca611f415c04b67e18c123ee440b1d69/coverage-7.17.2-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:4274acd35aae5bbbd0908792260819b9720b171ccc099a8168f7c20ef5d6ab3a", size = 260546, upload-time = "2026-11-04T19:51:57.068Z" },
+ { url = "https://files.pythonhosted.org/packages/30/8d/163e027635790ab232f3ec0261d2a26bec34c377bea2096554261444d0f5/coverage-7.17.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:85bccf6fe19ea3747147387971465f6a06197d6b9f6b644a97ba90ca5b09c5cd", size = 264877, upload-time = "2026-11-04T19:51:58.575Z" },
+ { url = "https://files.pythonhosted.org/packages/18/35/11dd585a06be350285d42c17aa2fccd0af18d962b6979f03dd1a258eb705/coverage-7.17.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:d5024913651813801fd43b65b6409b0d50f8bae95316c8f1512dfbd8ca96cb1f", size = 259858, upload-time = "2026-11-04T19:52:00.361Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/ed/f1f3b4bf6081122bf1820b1f38352377a75a2d5f39bb6331598e165b64af/coverage-7.17.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:99b64a12fe6017c254a8027790e0eaa22eaeec201e7dfda5eaac176a6984ef27", size = 262401, upload-time = "2026-11-04T19:52:02.02Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/bb/61e07a606a9f4d0f5391ac9b62b1f008a997f87860ad233f99833fd6e6bd/coverage-7.17.2-cp315-cp315-win32.whl", hash = "sha256:74d91af590514dda16165d5d7cfa94183150c6a6a80a99f61068369ae3959455", size = 229840, upload-time = "2026-11-04T19:52:04.813Z" },
+ { url = "https://files.pythonhosted.org/packages/86/40/2d4980d3374a023a34c5ea7cd5d2b93c02c5588a324ca7faf8a0a8c7dbac/coverage-7.17.2-cp315-cp315-win_amd64.whl", hash = "sha256:075566a44289afb634ceaafa7d8d377bb2667a5b4a90fbee2609efdcd015c66e", size = 230461, upload-time = "2026-11-04T19:52:06.727Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/c8/b307327b1256f9ed8ffc3271db041913835cfee065fbaf45105978093c08/coverage-7.17.2-cp315-cp315-win_arm64.whl", hash = "sha256:be699e76b57fede8920a0f568eff23b270d9004414b7cf6744bdda851638be6e", size = 230322, upload-time = "2026-11-04T19:52:08.191Z" },
+ { url = "https://files.pythonhosted.org/packages/40/18/0912e37f23c101faebd7c5e544c6ba6bd750715f81f2a2c7ea66f83a2295/coverage-7.17.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:ffbaadf8612781358b3f949ad68dce26c5a35beaafcc48125fc231dbcce7d362", size = 228380, upload-time = "2026-11-04T19:52:09.892Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/a0/c4ce9bbf82b2e9dbf2e0326004c8c37d87a275f515fc7bb22344a6162050/coverage-7.17.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d4004fce7b3a8ae5ef2af835670763f12723e9b9d74837697607b89a17f77312", size = 228776, upload-time = "2026-11-04T19:52:11.351Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/05/9c7429c87589534755398a58fe9837fdb2870a22d99e87623fcb261e1d4e/coverage-7.17.2-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a838a10afb5b60a6cc6315348b176badaf2409d821208a3e3cda1dc608129e23", size = 269129, upload-time = "2026-11-04T19:52:12.815Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/22/d1ad2ea7c8871d66f8453d33cdeec39986e6e1f2a104628e9bcb171f0209/coverage-7.17.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0fc17882975fe485cfc95fb2f20eed937e05e0c0d8ffa5048857cda3aa838e8a", size = 271967, upload-time = "2026-11-04T19:52:14.685Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/1d/5038de4bf6549903ec3d86959c6f18aff54911616b47aa96f4d6a5660c93/coverage-7.17.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a65faa1ee98e090a5fd7c116f6d3ab5c6a85fcc46849b8494835608715836399", size = 274667, upload-time = "2026-11-04T19:52:16.122Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ea/68a26b4b5eb255d92de906fd66e8d720b1df15e705949ea9a9cef2cc7f8b/coverage-7.17.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c17f716c837db550a202e7732cdfaa726b8515cba88be42193616913970d223", size = 276001, upload-time = "2026-11-04T19:52:17.639Z" },
+ { url = "https://files.pythonhosted.org/packages/70/86/0f4238f09b22bc03a907346fabcabe5449626879cdedbc1edd362ab0e570/coverage-7.17.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:545ca42488c7f0e62324fb2621aa05737db843b75e84e65a271bbf976f496f66", size = 269687, upload-time = "2026-11-04T19:52:19.169Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/ac/1885255425900f691c543d88394d9f0dacd2f2dc78422e660c747ca9c95c/coverage-7.17.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8de787f533b13aeeea5d7139610eadb56f249cc739840b973603d1d18d0fb023", size = 272158, upload-time = "2026-11-04T19:52:20.714Z" },
+ { url = "https://files.pythonhosted.org/packages/02/d6/0111601342d91e25d13e22a55f34600aa387c55b5027b90e78d941911821/coverage-7.17.2-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:485b2ed9e13b7e9209276843d7147e2f1cd1c816582655650a64c8f922093c58", size = 269182, upload-time = "2026-11-04T19:52:22.204Z" },
+ { url = "https://files.pythonhosted.org/packages/70/48/d9bdc5346a9ad09711f55b6901ee05b57305e6b2b66e6edfd50f81f0d410/coverage-7.17.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:a472e23a7514478e1c8124ea056424d681e3173975650b0a06b6181658f1009a", size = 273790, upload-time = "2026-11-04T19:52:23.677Z" },
+ { url = "https://files.pythonhosted.org/packages/98/75/3a4d8ce4aa59ed2e898364acf923b39471689d51876bb978c42e664cdb79/coverage-7.17.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:0a623de5c57c5c3f3aed830c73a3a5aad91dbf76d53bc30ec161be758c8ca408", size = 269277, upload-time = "2026-11-04T19:52:25.19Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/11/d15082e504e7852a25848ee0dcb59453579ec923c6d1f3010316e1fb6118/coverage-7.17.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2ad189c65706306644399e48530ad1aea1dfb311bd21d988df1ace422d09f3f8", size = 270890, upload-time = "2026-11-04T19:52:26.745Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/9d/6460101b0191afae9ed84c8d8136cc8cc09675f376ce08acb3118b8c27be/coverage-7.17.2-cp315-cp315t-win32.whl", hash = "sha256:6fd6f218e39a621a30dbaf9543831fa10fe713afc8a44b52fc42cfe277412609", size = 230148, upload-time = "2026-11-04T19:52:28.164Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/f2/3462d9616445a8cb36ec2e027d1c0ae82f6f90f224d9851ba0b22b7425d6/coverage-7.17.2-cp315-cp315t-win_amd64.whl", hash = "sha256:ef07aae0a666ef5edcab5bc1bb590468c6f4120a8cdad50a010d042a59ab6e77", size = 231074, upload-time = "2026-11-04T19:52:29.714Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/06/abeca0cf853e77aba9464d4ada4603c8b6e744b667c13e1d4eefecc487af/coverage-7.17.2-cp315-cp315t-win_arm64.whl", hash = "sha256:c68f64d96857749a215657c172f7144ed026063489686b901774f271997923c2", size = 230648, upload-time = "2026-11-04T19:52:31.267Z" },
+ { url = "https://files.pythonhosted.org/packages/da/91/1805cc144497d76a436f66a21fa57492bee7ed1257826fb992f03b9b2b6f/coverage-7.17.2-py3-none-any.whl", hash = "sha256:e674027ceb6bfd959f1bb035000fd20fa8b717dc6765adaaf8bca347822c8c7d", size = 219280, upload-time = "2026-11-04T19:52:32.726Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" },
+]
+
+[[package]]
+name = "librt"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/f5/9dc696772d241814bacac7880bac32f2930b5a6ebc1f85317b83161a011c/librt-0.16.0.tar.gz", hash = "sha256:ac38d6d8d66bf3d744148dbbc0b8e193e195a51e364ed55e224631f5721891fc", size = 219839, upload-time = "2026-09-29T00:55:32.891Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/0e/b04fd2d76b6a78a02286938251b2c119fb36ddece92c76cb4886ef7d44b8/librt-0.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fe4372c52d4849096c6cc1cda2817d293ec51440c890474ed59ef38d46556f18", size = 148058, upload-time = "2026-09-29T00:44:40.903Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/fc/e1ed24aadb32d38603c0c07f579d739eb4d90e64e90d525b024e4a999b4d/librt-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c72c5295a84bd249526da9bdca38f2e176d15c31c13bb0063c5053f4ca023421", size = 153073, upload-time = "2026-09-29T00:44:42.165Z" },
+ { url = "https://files.pythonhosted.org/packages/be/38/133ab655409420e0c34a9a9836a7f4e5ae75fba758d7fe177099130d58ba/librt-0.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be56ba9c884143495b517f23fe794ae367d58cd89ea0fdd6d437e3c024a87f9f", size = 493052, upload-time = "2026-09-29T00:44:44.043Z" },
+ { url = "https://files.pythonhosted.org/packages/94/42/aaa663bc9421f5bf7bbbe9d3316d7219749698ec7688ac3b519770a89c04/librt-0.16.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:ef46c1a29ffb8c72e882e22618ec618778eacd0578fb22c6e7cf9c11d15f357b", size = 485506, upload-time = "2026-09-29T00:44:45.479Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/59/83a6eb5087ba8b45b083859e2085256963e06d4c2531944cdd962670bfb5/librt-0.16.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d3c94211ee0c4f8d649ec06b7c115c0ec4eadb873a0e3154ca15cef3f814b071", size = 498708, upload-time = "2026-09-29T00:44:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/e4/15a8d62b2bc12935080ec7277e2e0b9b233047781eb784a51d3e25af669a/librt-0.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94aed6a8308818b91677957d1bd03188869cd7aeb23c5dba7912a6c0402f7602", size = 515981, upload-time = "2026-09-29T00:44:48.316Z" },
+ { url = "https://files.pythonhosted.org/packages/46/4f/287cc282ac5dd815204c0779e79401bb0a568c3a1335fb319bf0838fb566/librt-0.16.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:349c0bcb87ebd07481b6ff781e25cdc699723dbe2212e57dabb27f7a13b7b87d", size = 508670, upload-time = "2026-09-29T00:44:49.726Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/5b/ab7292add898ade0d2b5b75d32a923aae315861671aa5c39f5491309f7ae/librt-0.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:001bfd59a7d45b17e3e75f2a8c6405280b35e7b84471792778e718c4f368950e", size = 530393, upload-time = "2026-09-29T00:44:51.356Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/9e/8e6bcf027d4bcbc16a4e864b3d158a1d3e5f55ffa53af0238020cad89271/librt-0.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:28e038895b998d7a0c7798922ce8a1dc157675df5cf1c9ef0aca809ed804b7a1", size = 534302, upload-time = "2026-09-29T00:44:52.893Z" },
+ { url = "https://files.pythonhosted.org/packages/96/a4/8c5ab4f3b3a8e905ff8305d76976a2e666f52fe049edd398aed2402beb36/librt-0.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0dbe4096a7ecc00fa835d24510ad8545a4efef738dac96e0e63516783ccde905", size = 536150, upload-time = "2026-09-29T00:44:54.421Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/5e/7c4bc267e3cd4b8bd288de9f62d61fff203829c9cc649ea2aa12e9218cf0/librt-0.16.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5cd5b092441053364af968ea12084692cb9d4a22f3ce9524e377880bf028761e", size = 514316, upload-time = "2026-09-29T00:44:55.998Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/75/c8bc6b50f43c73819334233f5a3259c81216742cef56d2140c3dd377c3f0/librt-0.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ddeb3c9dedb461bb457c6c7d9aa7fbf35329da313d1a7543d00c8d0f3473c96", size = 557731, upload-time = "2026-09-29T00:44:57.581Z" },
+ { url = "https://files.pythonhosted.org/packages/93/fb/e241f91bc8de6c7ac1c6a20ea6dd381bc52312b44d711ffcd97140db1e57/librt-0.16.0-cp311-cp311-win32.whl", hash = "sha256:e05108e0849966f53a8d2d3112a7af881d0efaa479bc735bba91108f9f2350a7", size = 104922, upload-time = "2026-09-29T00:44:59.078Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/38/c892383d2bb1a6a80b2ec8ede84970a265a792c56614a06282bf3008517c/librt-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5f49cff01bd608ef7d97104cb035c75455e79c2d70bf4a506cf773338ac1860d", size = 125093, upload-time = "2026-09-29T00:49:01.621Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/56/8bbde1ed0b21480163617648f638fdaec9374cf5e9c846ebcee34ec02a42/librt-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:1d28ae980ae2218f9c5b95d191e947296f918c9bf0b400d467a9430275bbe678", size = 115910, upload-time = "2026-09-29T00:52:40.131Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/76/bbdaeb87b7c47b5c7343e90222b9bfa4e4a8a83f647e02933ad0129225b1/librt-0.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe52bf4641069e7978a14253b036cb9002def1926317e710f2e249f8a8c47742", size = 151028, upload-time = "2026-09-29T00:52:41.508Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/0c/ab8ed3dab0085931aec4a792c7eaac8dc6c5ff4691fda3a5360d9d8a9cd2/librt-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5bcc2c4726ced915b00de0c9856a4eeabfb3fddb93e10e0b8f735b7709358b6d", size = 155283, upload-time = "2026-09-29T00:52:42.873Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/36/494e79d460c80c1f030661e8287c9eca5e1ad652dc2b2180b6cd42abce0a/librt-0.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff7baa55f8e7c69851419e50a666015d02a74198716fd45c0125a2112e0a389f", size = 503185, upload-time = "2026-09-29T00:52:44.638Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/34/a8464038dd9db6e4381fa2b6eb73dc9a50888d77102c4c139304ac35cddc/librt-0.16.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b95d5d92ab83d39e760a52091bb1baba664f3a2351e39b1e16801e5747c2f0e9", size = 496738, upload-time = "2026-09-29T00:52:46.441Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/53/e0e5e334ef0c6ed27039d323819368b9ef6712be87d55ee2bf9799398afd/librt-0.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6d085d70bce51d43c5c7c36d63490770180d8779e71c49305c87b4213918de7", size = 513818, upload-time = "2026-09-29T00:52:48.068Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/f7/7ce72cbf19d0addd05090b339152fd0548a02562c2866a603e6e3b3da2df/librt-0.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36e53948e99bbe3ffea257124cfcae1cfb01831555c9a9c903c9f9a72db7fd07", size = 531952, upload-time = "2026-09-29T00:52:49.816Z" },
+ { url = "https://files.pythonhosted.org/packages/83/22/0b1bcb6a8e723c8b4fd60dfc8ae8ec6461c54073fbc8685efeb8d900d407/librt-0.16.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:54d11f726aae9df5a6ffbbf0a03a52449bbac84a53ef03669cb41cdfd4ae41bf", size = 524465, upload-time = "2026-09-29T00:52:51.354Z" },
+ { url = "https://files.pythonhosted.org/packages/64/2e/e9c23b8b9df1813da1be205deca9606beb7ddd033972246cd426d05374a0/librt-0.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4323193ac0cd025f85af531df8ba91bf24d1973b401697347a6282e8fd3fcf5e", size = 543191, upload-time = "2026-09-29T00:52:53.284Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e5/6a8b21b342c03ed7e230fa3afbfd2edc58e6e87ef1f0d11fa2b9a748c252/librt-0.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e42f8e098b9c5396fefa05fb1cc7e33b0e08fc51da106b5de4a45fd22aac6743", size = 546979, upload-time = "2026-09-29T00:52:54.932Z" },
+ { url = "https://files.pythonhosted.org/packages/84/9e/b5129023eced1be01e01c22757f53be551d463b1bb7264f787927404c1d6/librt-0.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:39ec1d5a14e37baf1450a6cabf03fe552340808bf1ad9d71824ab90117716459", size = 555317, upload-time = "2026-09-29T00:52:56.869Z" },
+ { url = "https://files.pythonhosted.org/packages/71/89/28bba5938c725fe91f06bf93f7fa6c6b150229df53a87b454b0d5c2a796e/librt-0.16.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d1aabe3925cbb4a08d15b7b20ba4011b53019da0c4173a25155139b7b1baed65", size = 535846, upload-time = "2026-09-29T00:52:58.475Z" },
+ { url = "https://files.pythonhosted.org/packages/22/92/63773026614f888c5d4e370e395ce42ca604b89f70b3acdfedbf94851b80/librt-0.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:300c3ffdc459f4a779a8411ecb188e3ac0b1ff3a3a7b099642555dedae06c69b", size = 573700, upload-time = "2026-09-29T00:53:00.127Z" },
+ { url = "https://files.pythonhosted.org/packages/66/8f/347d4677eefb57cd9f8e01d95a1dcee9a91b4e44e664173c32ff6f3752e5/librt-0.16.0-cp312-cp312-win32.whl", hash = "sha256:c17194318e4c0c0348b36f36c2ec7534436fe0a4c15582403162a4f08c80797a", size = 106158, upload-time = "2026-09-29T00:53:02.031Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/2c/5193dc81127cd5ddfad031391b046bf32dda219b38463ab872407ca30646/librt-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:25a58a19ea8d83b68209f04912df765e9260635ef77646542ed4b4abe6bc7940", size = 126176, upload-time = "2026-09-29T00:53:03.445Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/3d/9668a400c8dd81d162eba38b33fa49fa6205f1a64493570a13fbb815c3ee/librt-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7be7cf555bc30ec12622e9447299cc4a9b8ff307548b634794353db0c2065dc", size = 116572, upload-time = "2026-09-29T00:53:04.815Z" },
+ { url = "https://files.pythonhosted.org/packages/46/cd/ae5e0e9dba45d1399aa04a5395bcc0bead40d9fa06dc903634a7b4d7473d/librt-0.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c5e6144e68b577f157519f2ba88ca20e3ed61c29b00e5cdfa76cd2d45acf059a", size = 151036, upload-time = "2026-09-29T00:53:06.284Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5a/48a16e323c5f9447a94cce7b59babf60fa04e62c3365ecf060c77ed8b320/librt-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:33f41443a1f4e1f099331b3d8120e409fbff84b9760bc1cc9ea496f37ddaa5cc", size = 155211, upload-time = "2026-09-29T00:53:07.72Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/29/0f59299eb4251a409b2e690ad4b7d9f8a676db829d7817ec961f32b44f7e/librt-0.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e510b7770bee609617a3374a96548eb114cae048023e3f049ee449e7ff2db32", size = 502587, upload-time = "2026-09-29T00:53:09.191Z" },
+ { url = "https://files.pythonhosted.org/packages/de/ba/d6fb4ef8d1537c396079d72289f16be7cd35a366e5065c51253fea2760b6/librt-0.16.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:efc49c462d4516b8a58b00b490078fa64689fd1fe66970cc190131d7afb8027e", size = 496146, upload-time = "2026-09-29T00:53:11.016Z" },
+ { url = "https://files.pythonhosted.org/packages/52/fc/8c50dd4d7cc97c0ee8f252c8a3104980f234391cf1519b554e8b9de08b60/librt-0.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:92caf82ebef5e12d21c72242b70d1e92536f1711cf2a727a4c276de4b4469087", size = 513336, upload-time = "2026-09-29T00:53:12.655Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/59/16c409c56f708eda2db9a0553662845d45e3871c77d70a240dae3f3bdc56/librt-0.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17bac7f7a16b328fff77e440287693eb017abde913595b5827ebccbc21ecd8a6", size = 531689, upload-time = "2026-09-29T00:53:14.39Z" },
+ { url = "https://files.pythonhosted.org/packages/88/82/d34772a6c29d1446dcca6e64d74062efd508523ab351aa16625a4689d5cc/librt-0.16.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5b976054553670829985ed767feb78fb6bcede0175327c4844dd5c281c1be659", size = 524537, upload-time = "2026-09-29T00:53:16.064Z" },
+ { url = "https://files.pythonhosted.org/packages/77/8f/24c5631313746131ccee53bc91fdc8374f9cf25e0082a1fee9c93bb98acc/librt-0.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0058f9d68721094105917254c72ac0569117bb7b13b9769cf45d26d89f9d21cd", size = 543231, upload-time = "2026-09-29T00:53:17.939Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/cb/5f8e0d41dbd8b499c2265e939c31acc9ba59845565bf99539ad1c06aebcf/librt-0.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b7beaf3f4487b7d8adef1f158b49067cb4d5a19fa7a3bf31a4e7a820e435c5", size = 546499, upload-time = "2026-09-29T00:53:19.666Z" },
+ { url = "https://files.pythonhosted.org/packages/be/61/063052de441d1385f59cea4223f184bf9e5d125de1ae3239b490aa1e486e/librt-0.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:468df902df016a06eb0e40b0747dc8d14e47d7a38b18b63b1fb167d85cb94d63", size = 555261, upload-time = "2026-09-29T00:53:21.292Z" },
+ { url = "https://files.pythonhosted.org/packages/14/11/a2ada0529372268d6401afa9d457a095b68cd7753532b6f7f33049a19b43/librt-0.16.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:aea7b1f2b125dad5de85f049136651bff256c883c65e6b9209b2da0a1ac3cdef", size = 536029, upload-time = "2026-09-29T00:53:23.07Z" },
+ { url = "https://files.pythonhosted.org/packages/23/9d/5bb6d38853382986dca702fc7e06c8256d30f5fa0676d882773b744610dc/librt-0.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a8afb6557920860b7a3a596eb804cf37e09e7cf8a803db2478c202acc72d8c2e", size = 573748, upload-time = "2026-09-29T00:53:24.696Z" },
+ { url = "https://files.pythonhosted.org/packages/99/f6/0025cde35ff7f607684dc775a2b2d732cce2561cec050a6ee1fb2e1fc6fe/librt-0.16.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:77c7a2b4fe2c1369e0d5aa1cade26740a7b14be32fbc9a5535d617d20065c39d", size = 79861, upload-time = "2026-09-29T00:53:26.089Z" },
+ { url = "https://files.pythonhosted.org/packages/93/93/303b8592909bd583f83f02818ffbea3f7647ca1e22b1cd5465d04b8145fe/librt-0.16.0-cp313-cp313-win32.whl", hash = "sha256:02d89c813d5ff74b17df72d3a34819d132cd168e56b81bf755b809bd9e46b8c4", size = 106236, upload-time = "2026-09-29T00:53:27.43Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/24/80bbb463c60ed18e29cb26aba386ddb76609580e4e7160710e550587018c/librt-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:14ed6ebe3e4f85f326d7920011ad30ff49ed9334e62cf88caef9ba973d9e3a92", size = 126180, upload-time = "2026-09-29T00:53:28.7Z" },
+ { url = "https://files.pythonhosted.org/packages/43/80/b1a6fbdd7da825cdd55c71aa81eb6cfa82c513c360774152eacb50b4a771/librt-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:83d4041a3d9b2fd053a8a4e1f22878b3e5833e2712956382d5c048d791454e91", size = 116557, upload-time = "2026-09-29T00:53:30.012Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/93/9e0cf7da129a93c3dc7f45bc3cd4a660f2aaa995aa8a6f95c2583ef41239/librt-0.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:931a0bb0fcac88f263e269e46eb30ba8e21402cd3c62ca40cb97034c0693fab1", size = 149844, upload-time = "2026-09-29T00:53:31.391Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/26/8a90d2a8f2b2e471bb486b7aec117b8ae622715ed6c39853aec48ea20073/librt-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1bc17e54e5305f8d40b7ca203671ff5a9e59c1d0f8ea0f625dcca53a3984de11", size = 154111, upload-time = "2026-09-29T00:53:32.718Z" },
+ { url = "https://files.pythonhosted.org/packages/35/ce/67abb46258da4d3e42ff5b141db6f38c59357bef84c7979f183b22f924f1/librt-0.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:877698bf6bca5721d8be345f2fe09778e40ecadea8b58c73075f2b1a53666bf2", size = 494234, upload-time = "2026-09-29T00:53:34.466Z" },
+ { url = "https://files.pythonhosted.org/packages/12/f9/ea7162414a16f8f1bbd3b493ad6d22b926c5471916e078350bdbca8c4e5f/librt-0.16.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5981c011b306781ce561e18e14230a14524a3d8109b97553666c942c18f31a96", size = 491173, upload-time = "2026-09-29T00:53:36.124Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/09/9b3e869060dd33f9989b80ba4fea306f6db8cecefcdfe6d7346ef0603f65/librt-0.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:afced3dfc17cd805ecf7a3d77996a71cf5f2c75aa66eb0c21a9930f4fc992f86", size = 505547, upload-time = "2026-09-29T00:53:37.686Z" },
+ { url = "https://files.pythonhosted.org/packages/50/07/79007d2165f649ea93e08c0962d1d62c70af9cde77965255095bf9d96f9a/librt-0.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca8052401c55d7511dda6760719fda7618067e83535d7d0010096d216c34b667", size = 523057, upload-time = "2026-09-29T00:53:39.301Z" },
+ { url = "https://files.pythonhosted.org/packages/61/0c/8fbaff66d0ba376d8864653f5acce1569bae27e89648671f26f7eca67ab8/librt-0.16.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1e511762a074005bb0aa569166779834e75e438370226930d0ce1866d4b6a33b", size = 515158, upload-time = "2026-09-29T00:53:41.012Z" },
+ { url = "https://files.pythonhosted.org/packages/df/2e/23ff0dece76f07a4124413a57682efa0bbeb5765ac0122bc0955513f82ca/librt-0.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1e8591bd8a5a628cd7f07954c6a1592359a878bf032957a8e9057a41d644311", size = 534183, upload-time = "2026-09-29T00:53:42.451Z" },
+ { url = "https://files.pythonhosted.org/packages/26/c4/e11dea21d9a29486eba78887380374189d472734fa32cc50cb37ca44d3d0/librt-0.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4aaefb4ba6c07e1aeebb2795c8958148f1d6f9af3b555b53d23d766edb6d67a", size = 540663, upload-time = "2026-09-29T00:53:44.272Z" },
+ { url = "https://files.pythonhosted.org/packages/44/75/e873ae158a8b7f5359be33e7fd6c1fbe02d9a78a3e89a77de6b0e837f476/librt-0.16.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d92db7a0f6aee44f1baee94750457e8d2d1c6ccea41842de6268d34e8dc7eddd", size = 545908, upload-time = "2026-09-29T00:53:45.812Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/36/8939d3f6a93e11bd9592e6fe28d2b44f1c2dc4bed6e22e72359a91e18ffb/librt-0.16.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:378dfaffb38e59c24a87cde5713cd865d51ff7383fa12947f3907f306ea1ca55", size = 523302, upload-time = "2026-09-29T00:53:47.596Z" },
+ { url = "https://files.pythonhosted.org/packages/71/14/35309f44a077f0f42ade0e2e7cd88c0cea760c661af205c01ea90d3c0e1f/librt-0.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3e0c39bdc85370422e8b637be76eb1fd07d30967551b03e62267dd156f553152", size = 565911, upload-time = "2026-09-29T00:53:49.278Z" },
+ { url = "https://files.pythonhosted.org/packages/78/0c/df6255b94967f3159ebc46f08d8e783c12da6ee269ddb74b2efed63c640c/librt-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:1b384b90ab79a7bc30b566895809a636e0666f21f3cf12b54823d025b7e83839", size = 79119, upload-time = "2026-09-29T00:53:50.938Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/44/d30d5a5461378c9d33f36736c6791c3b4c4ba4b1ffe0da7350aedcb2c9a0/librt-0.16.0-cp314-cp314-win32.whl", hash = "sha256:52327da75a94012e7f932f913d20d3876bed3c102be00e6c3e8600ff7bdd58a7", size = 100140, upload-time = "2026-09-29T00:53:52.205Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/98/769712f356a1e897df3581bb0c3100375d054a000de26099360ea65b5111/librt-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:3f0b8114c44b2ac06ff5dacd08e07e8e807ff4f46083f2a1602685122559be41", size = 120464, upload-time = "2026-09-29T00:53:53.495Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/d3/ae2abccc8bdc8b063c1613a77686e17b74e9d3d60cfe6f12c63fa2821b9f/librt-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:8caf96a4ef8fb27d0ac0d1ad8337d26a240acd4a02fe4345d0a8f264753e8f99", size = 110889, upload-time = "2026-09-29T00:53:54.817Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/26/0737d4be058dd6376eade7dd8b380d4b869b6394cee929393a5a431c45bb/librt-0.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:953107e2f68d0f3512c48f898b0dbf0ce5cc52bba0f318d847c985dc555ee4cc", size = 159838, upload-time = "2026-09-29T00:53:56.22Z" },
+ { url = "https://files.pythonhosted.org/packages/01/96/9bc96531d7c620e9949af904470f02e3fa8f35129ab8e8f281c51eaa3788/librt-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad37d5b9abd49c9a655dcda7ea52a8a752884062ef1ee71ae17c2f2a0f81fe6a", size = 162018, upload-time = "2026-09-29T00:53:57.532Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/fa/b0289dcb186eb3f97221ba00da5f4bd3ba7fa5e99752d48aa8d336615334/librt-0.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c4aa329c17bd1aaea4f6e89335d8ccd494b3a5830b6654462273e50e11023f0", size = 703983, upload-time = "2026-09-29T00:53:59.024Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/ab/05ebbbde7530fc5eeb58fbd1581522a9f64f132fa703c4c595eed6e14760/librt-0.16.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0ead24d2562a49473dddd9efef8581f020007eb0054389c3ee3ffad38b1ca4c9", size = 683847, upload-time = "2026-09-29T00:54:00.671Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/75/f52aeecd4dbadbddf80725ba7de126d8bd5d0eb66247eae17a81ed90dd4d/librt-0.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02118f56a9c36ddd07dfd9b919d9ecc117ba20a90987d56aa4c429fa34509188", size = 698045, upload-time = "2026-09-29T00:54:02.247Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/55/fa277a835cd6eb42380591ceb85f48c5b4d2b2d7e2cb9869e1e17d24d237/librt-0.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e29522c62e28595ff7e324c6834ade51127707f0e255b18d1c1cf03d39c1048", size = 724640, upload-time = "2026-09-29T00:54:04.078Z" },
+ { url = "https://files.pythonhosted.org/packages/25/e4/2cf64354f3fde8ebd591b3b48f96510bee24ac5f7d1e567adbbe210abdbd/librt-0.16.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ff4b2367926b69c6215635902cccb04048e73094e9862900d27cb2c6bbff143", size = 731748, upload-time = "2026-09-29T00:54:05.741Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/c9/c180af3e94e01aa529fa93d7733fec2abc47f222345400cf21d5481d5f8a/librt-0.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c6f1b27bf1632a7e016af9f145f82be95e1edd7721a646505c21059257cb5a04", size = 754960, upload-time = "2026-09-29T00:54:07.38Z" },
+ { url = "https://files.pythonhosted.org/packages/95/d6/01073aa78c58f356b10d9c57b3fe9abb143df338142bcd316df417b89db0/librt-0.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5696d7f52e7b37217cb3a8f92c744fe835942602fdd4c1a8bc4741d3bfdce15e", size = 747000, upload-time = "2026-09-29T00:54:09.06Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/d8/1de3783908658d697a8cfc00582f61299ffba7796d7260c112e4700b1109/librt-0.16.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6072e92dd876ff6ceeb6cf371e35e51f479349837391341f479b08df4564242b", size = 749569, upload-time = "2026-09-29T00:54:10.702Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/32/e817f66c96d6caa8bb8435ff93c4c220624d59efc506be59ab98fcd01d0c/librt-0.16.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:39ca4f2f2fe05de8e63493da592d84311adabe5bef52b193851981da9816b302", size = 729483, upload-time = "2026-09-29T00:54:12.25Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c9/23992ccd2b9d22798fdd0f61353183a47414e651da782ad83eb680f50833/librt-0.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9807485a908f00355820f18e91e045ffdcdc5adb68aaec40a1e2b88c5f7bba1", size = 776584, upload-time = "2026-09-29T00:54:13.837Z" },
+ { url = "https://files.pythonhosted.org/packages/67/3b/e8af957f08e6e2e8e566b099e43d2748418aa565df6ee1d9d3331209fdfe/librt-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:94be5cb7bca4df6201f4183e9e4fa2086c655283d20b38cd84500a69057575a7", size = 104402, upload-time = "2026-09-29T00:54:15.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/1c/946e6443d7cd32347a086043395e421e52fd603c9163d2ec970ceab8eed6/librt-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d46ca272b251d033dd4527b0dec5f261a28a52bd5fa0f99c117b0a1f8588cc2d", size = 126227, upload-time = "2026-09-29T00:54:17.165Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/a3/bc4f9959d3c62bcbf9e5fd3470a8bbd8bf33224ed4c25d7fba173201b8ac/librt-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b9d6d4b14e92d876f8026b54c20c445f36425214c1081dc76f74e40db386b82b", size = 116493, upload-time = "2026-09-29T00:54:18.567Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/4b/10fdb42dfab4c1533e1570e686b18e86ff4328b406361b39fb3016667638/librt-0.16.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:6fe436af2eaf630474f491af5d032cbe45f93fcff5c3b9fe4ab194a7255b20ff", size = 149829, upload-time = "2026-09-29T00:54:19.933Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/30/a90ca13f1d3d91af1680000a4038536907018fbd51764767287a05d28b8b/librt-0.16.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:8ff5d26c529336be9bd7ae04483235d77778ee7d6444a95353102b542601ce81", size = 154522, upload-time = "2026-09-29T00:54:21.306Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/dd/bcf364eacfa070bb1fc88d503111ae7177914197ba1fa717c748926e7930/librt-0.16.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:909d8e3c1faee44cb762b1c519ff8613dcc5ceae5c99987a00917b5a31fd1d6a", size = 497593, upload-time = "2026-09-29T00:54:22.798Z" },
+ { url = "https://files.pythonhosted.org/packages/18/c1/2c4e81e347bdabfe8346bfc6dc37ae5e154d61c88e30848ec0606025a5e0/librt-0.16.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:6d4a64283ee61824b5790de882bc68e2d9d7a5143537cb7a966f7354f71646d4", size = 480459, upload-time = "2026-09-29T00:54:24.364Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/d7/fef2a3cb8400701be496f6e459876f650b3451f407e30cb243de571ae615/librt-0.16.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5810ba811297fdf37a1531a57667cb8ace0842013ca8606bf9eb7c24cf4be154", size = 507814, upload-time = "2026-09-29T00:54:25.997Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/6f/53762927a32e9dc9eb1d1c1f3528da281290c86d96929a8af652f671266e/librt-0.16.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e56aaf8c167548dc8e5d6f3bd0f48dcdd299a23c73be3f744aab79d99e9c7f5d", size = 525114, upload-time = "2026-09-29T00:54:27.747Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/67/0b9d031f303c4e8c691a9a8ef9d272f13b530c11cc563b819df621b6a348/librt-0.16.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f36c58e33b304b525c6c9c5076399c6ebf1109e17b9051a05a407b091b9215b", size = 520465, upload-time = "2026-09-29T00:54:29.331Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/d5/2056a3a85864e882eb17a203a10ddb26fa748bc9718ec67e79059ab46cae/librt-0.16.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:242e00b3d4fa37c3d3c1ca5f5c9adb7d909ddb1eac9c41f2787320d00caa0af2", size = 537257, upload-time = "2026-09-29T00:54:30.915Z" },
+ { url = "https://files.pythonhosted.org/packages/54/57/e0d79790c163cbc0909e209a6f62e30bb713b647f905a176cdc64848fd4d/librt-0.16.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:0253721561787b8df8443eb347b7a6461015354e5bdd37ee38a41fef220d2bb0", size = 527442, upload-time = "2026-09-29T00:54:32.478Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/50/1c0c95aba7af51f4752ea34fbf2eb79b36e7cae3a72536248e8c735de735/librt-0.16.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:3e483a8d69ede8067db70c0e83007423b6925de6fd53afed01d66160f2e9398c", size = 548139, upload-time = "2026-09-29T00:54:34.183Z" },
+ { url = "https://files.pythonhosted.org/packages/98/91/a8a43dd5138d4f55f88846b8f0c85454a4fad69952cebfcff9948f831290/librt-0.16.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:69ba927445cfaaffb4081003ef5224c55a5c2ab67ef956f416ef744916e44121", size = 529708, upload-time = "2026-09-29T00:54:35.761Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/41/d5226881ab2b7c20d9d587b37bdd4a0ec8775a96d00ca87ac9f385587db4/librt-0.16.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d6a365f2ab45a984d0e00eee0dd17f599ceab8cadab6ea07b6111c8132fc0e42", size = 567781, upload-time = "2026-09-29T00:54:37.409Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/bf/2345ba57a626e8c78c4ddcc724636a8df5a593fe46c1ee76bbf477e32b0d/librt-0.16.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:f01f3805f2dae4781c0c34b440e31740d082950bdaf89a6f601ad589a28af57a", size = 79177, upload-time = "2026-09-29T00:54:38.792Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/40/99e77936cc9207f629b077bf7cbc5e2f0827cddd7c29792f04ef881bd2c3/librt-0.16.0-cp315-cp315-win32.whl", hash = "sha256:b0e3e721c75d2e79a76d4422c79d7ba705fe1bbafec907037fe7a657a480a0e3", size = 100127, upload-time = "2026-09-29T00:54:40.251Z" },
+ { url = "https://files.pythonhosted.org/packages/56/1e/801fe26bc622061b9dfd010e166d94142cb774b733217e6d98d1c0cf2638/librt-0.16.0-cp315-cp315-win_amd64.whl", hash = "sha256:bc02954b1295de798bbdb0b4e2d8a28c2117de8b5c73dcbeb27dc32572dfb971", size = 120480, upload-time = "2026-09-29T00:54:41.722Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/a9/d533983055bd36e112627384c2c038845d1df882540b8bcefb566475640b/librt-0.16.0-cp315-cp315-win_arm64.whl", hash = "sha256:c5db585d43449a5f54303d4b2774e45e1babd975cfe1630a3d708c0b80c3e560", size = 110915, upload-time = "2026-09-29T00:54:43.052Z" },
+ { url = "https://files.pythonhosted.org/packages/55/fe/d62238fa9c653b0e0613290349467cb65711b5800e8e474f45e942a0ad95/librt-0.16.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f06c689cb14afd9b612727553a5ec5a40febf113ca41c4413a2b0b334285884b", size = 159714, upload-time = "2026-09-29T00:54:44.497Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ac/f31efe7818700be72ba4f9af8a80fa67c39808c8d26dacc55dc1f6f35172/librt-0.16.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:13b4e8aba90b0b1c82474e9844aa9ffe7ad3faa484350e1da64cb8188d903134", size = 162086, upload-time = "2026-09-29T00:54:45.968Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/16/4d7487bf86a9d7e8e18f37ba5538789233ea693637379b75701bd35ec9de/librt-0.16.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a269c46ae327d8e6f8c1f85f7516cb52c0fa48127565a1105a4f4a05ff2a0b4", size = 712727, upload-time = "2026-09-29T00:54:47.486Z" },
+ { url = "https://files.pythonhosted.org/packages/77/74/50cd550ccc1a517b9ed62347625c01ddf8c4afad66490312677c011458f6/librt-0.16.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a33e0dae1f8592146a4764d54ce842b278732d21a84e17c3bbe6b1bc158a2248", size = 681106, upload-time = "2026-09-29T00:54:49.126Z" },
+ { url = "https://files.pythonhosted.org/packages/df/5d/7293f712975ee6fdd2251411fd9ef1c62bc99c7b83ecbe62dbc999b1fab7/librt-0.16.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:47ada6ea32636492c61aa8ad27ae3b9404bfe7a97e3ba946d1984236cc741da0", size = 705921, upload-time = "2026-09-29T00:54:50.905Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f7/8aab946f11d59d1bece9ffc65d994b200c789a8ca5a95c17e17e609f912a/librt-0.16.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43bd6e642d8a248c114327f98dd25ac5a7cb5aa168ef02f0559b91874df16b8", size = 731786, upload-time = "2026-09-29T00:54:52.584Z" },
+ { url = "https://files.pythonhosted.org/packages/43/76/1c42ab31e7cb8384ebf6d3af607213c495222474f4940443ae7639ab7685/librt-0.16.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c3d1bb7841a816ace6449bb26d3f9560dbfa20e71c568d23f0f62bf1e68f50b1", size = 744964, upload-time = "2026-09-29T00:54:54.217Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/2e/4b19982d933d2dfced671e840b219e4c1cd3f507df6e6dabb51bbd4e3850/librt-0.16.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:3931f7a3db322e7f44e02a280e3949326ce9579ad388ee8d691dc7c76da9fb70", size = 765305, upload-time = "2026-09-29T00:54:55.815Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/1b/e872583de2dcb3ac7746e7a2321aeeb168274f3952a87dc66e05ddb29faf/librt-0.16.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:f4462528b6000afe8f16907b5c7c2553abf1df005ba5140e6eb394541c3624c3", size = 744959, upload-time = "2026-09-29T00:54:57.532Z" },
+ { url = "https://files.pythonhosted.org/packages/10/de/a18c6bcfb297af2674233e90b3c3661c3a0af0f1434a0c966d2ef708a835/librt-0.16.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:80039ba9b6a7d5f1a0175a4cca6bbefead87bd854c80abad1cb30afe47a830db", size = 758138, upload-time = "2026-09-29T00:54:59.484Z" },
+ { url = "https://files.pythonhosted.org/packages/22/1c/0df1d732539c297bb1a093e1fe3204d2faf76a9022cf4e1d1c2fce059790/librt-0.16.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:7a1d272724b581bb6bc769dfdafed6da2ecc9886ba2450311de55a4ac2e1e9cd", size = 744268, upload-time = "2026-09-29T00:55:01.179Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/f7/ccaf31331f20c91a5bd9bd48ffc3f743f9c81bb5720cc7b2a720ca204f0a/librt-0.16.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:fbe4fb8c5445f7496d7f7f6bb0807875d09d47e6771ffa175fb2df2895fb86ba", size = 785783, upload-time = "2026-09-29T00:55:02.938Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ea/7421d9e6db894cd6cd788b604b3394a163ded6b942052b6f96c23ccf08e1/librt-0.16.0-cp315-cp315t-win32.whl", hash = "sha256:375bfe6b572a8f6cfc398709356046173bf27e64c4c5edaf5f7062f051fb4bf9", size = 104355, upload-time = "2026-09-29T00:55:04.533Z" },
+ { url = "https://files.pythonhosted.org/packages/85/6d/7c31a506eb847bc58aeb2402d58e17605e821f68ab5df5e66fe274a6df41/librt-0.16.0-cp315-cp315t-win_amd64.whl", hash = "sha256:bd3150023d3dc2bc70f3784e59ffa1140d56ddba3d8125b3d6f9f85221279bfc", size = 126134, upload-time = "2026-09-29T00:55:06.017Z" },
+ { url = "https://files.pythonhosted.org/packages/36/69/7a5d10ac409c4da0355e054a14371871da9b5557fcc42772cd00181c6cce/librt-0.16.0-cp315-cp315t-win_arm64.whl", hash = "sha256:8ceafb70f2a4f0826f11031942e59c0728fd98da112dc346d4352bde1e486866", size = 116391, upload-time = "2026-09-29T00:55:07.484Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ast-serialize" },
+ { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
+ { name = "mypy-extensions" },
+ { name = "pathspec" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/3c/78ecece71ee4b5445389a4ccd11627cfb9c69e4de554d11060171f376305/mypy-2.4.1.tar.gz", hash = "sha256:63b590965691e540a335fa74dadf31a7069142f6b72e234689bbd244edf55623", size = 4066728, upload-time = "2026-10-28T19:23:13.286Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4b/8b/2bc7f489528db504221ad7eff6b3f83979ee57d066103c72a85d09046a30/mypy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:79a1e79199e980fb41f219cb3d4746d25c64b0b623840d3ed57df1cdc13a6502", size = 14321495, upload-time = "2026-10-28T19:21:16.612Z" },
+ { url = "https://files.pythonhosted.org/packages/18/9a/b2f037b4982dce33645296e4394b031ade60f697f468761fa3ea8e2f8ece/mypy-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e18d0da3898b3bcd16192f15a206e7b8b8dd0f4a0b3e51aa5bd5c2afe749e2c", size = 14467468, upload-time = "2026-10-28T19:22:10.629Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5a/379f9bfa18bf3f8d43668fbd9ae4f2922dd027a559c461ca9d41cde1d32e/mypy-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b69a15fc2e75858d6b15e91ed3a0dcff1a533265183b64734138791fe0f49aa5", size = 15421496, upload-time = "2026-10-28T19:22:48.005Z" },
+ { url = "https://files.pythonhosted.org/packages/83/50/3bde18a2824ec296432cebe01752ccd0ab8a2ad784abe4390040d019b3bf/mypy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70971cc0bcb1d3f03d2eb68fa430a6481ccc03558dc001d9534d7e5fa274c392", size = 15670425, upload-time = "2026-10-28T19:21:57.026Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/36/8b722b88c325d317354de7d10d8fb9d4dc0f958433e873a91fc4ed21a06e/mypy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f7225951a277dd0d6add900731d487174b7d24ac133c09d670ed63b4d8cb90d", size = 11300972, upload-time = "2026-10-28T19:21:48.724Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/d6/fb2bab1245658d68af8abfe776362a780c621fe0f364f2c7b53039a945ee/mypy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:be4c03f33c55500191df2ed4a9b5e7edbdd1fff067fe361a5a9b1b5d3262da6e", size = 10873349, upload-time = "2026-10-28T19:21:32.104Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/1a/0be9dc6af5e0e04128f18a5e36b4ca44bfb0085857d37aa6a7de1ac2b0cb/mypy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d63737426a048a27705a4eedb828669f4bc0873612a1b46b0b9fe892b2ebaaaf", size = 14381321, upload-time = "2026-10-28T19:21:54.223Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/a6/7907e36711a8a8c703f961f076fab61793383694140bd1fd87589f3f68d4/mypy-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7344869cad05f073b8cae2826774fa0161e3eb2f5c685b76813574008d77d750", size = 14466373, upload-time = "2026-10-28T19:22:25.211Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b8/7313212b29de570c25c9859d7c8e4ef2251a6d34338a1e20031d94297721/mypy-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a20ff6cd2af7a670c64f089bfa5fbcebe8ff3bf896c5e6ac7c9862c7960e4494", size = 15553666, upload-time = "2026-10-28T19:22:36.511Z" },
+ { url = "https://files.pythonhosted.org/packages/23/8f/92966b6082ff49da9142e1e527eb9af4359a9a61632a1cfc313da96b37fe/mypy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9ed1195502cca32be5ef756b1693477f4991db2f2e34eaa90cc4ad6bea4f9531", size = 15802641, upload-time = "2026-10-28T19:21:13.421Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/b4/874f6fd54b005020ad63e2cf7a59b178d16a771d478849b29b842cb0cf14/mypy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c194c5d878130fca1ae662dfb1feb6274afbab0b40032c80147377e0ea803d5", size = 11383067, upload-time = "2026-10-28T19:22:03.516Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/41/5ae8e6528c2faa96e4a921a999d2c7ac7c565bee184b4282bf62ffa76b5d/mypy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:71eda06e5bae51976ce869fda429159c9cec678bd02f25bb197f0265c8f991be", size = 10946882, upload-time = "2026-10-28T19:21:39.51Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/65/cd19bc06b67d2df4828220245fd98569ba40fac00c60ba48bef1817d8ac3/mypy-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f1613bfe6726169a5680ec3dad5fe1f6d96285c12aded51ad432eadedd39d45", size = 14214179, upload-time = "2026-10-28T19:22:27.49Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/db/4d54ca4f49a0113d0c956980272aefdc6eec5458f7a72b350785b9ef2249/mypy-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:680ba5d9f0e3fe0c746b3d14df15f2a718e4bc308a1145616a1ea691ce7c354b", size = 14376217, upload-time = "2026-10-28T19:21:08.019Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/45/089b4724b85f0f408941be5298f817ee89181c1ce031a73d60ebb150c2c7/mypy-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb70621535c22fd4b1a0450d37ed1d6c5eb39656a1cebfb189084c12de54ba7", size = 15467623, upload-time = "2026-10-28T19:21:21.555Z" },
+ { url = "https://files.pythonhosted.org/packages/27/4c/15edbc64b0b1412bfe94c74192b3b56554ab642b85a31bb06cef6313101f/mypy-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4af5ad558033bf71a44a1be149ed61633522824f84c1b6bc2445d3fe2d2a5296", size = 15729867, upload-time = "2026-10-28T19:21:37.557Z" },
+ { url = "https://files.pythonhosted.org/packages/76/5b/7d160fb685994b65b0ca86d352f3afb5424f96a9e6914719f7b861fb6701/mypy-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:acdd82cb3a2eb10720e87706a128d75785d005a8c47e36a4973221524f41fc56", size = 11390802, upload-time = "2026-10-28T19:22:18.081Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/ea/453d27f8d531f57c5f14aa73094eaa48e3fb77f680bdede2045cb64fa58b/mypy-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:133886ef1a7a8cf673df55d865e41580c6c46aa9b8e31e1325dfe315c038f745", size = 10950427, upload-time = "2026-10-28T19:22:59.003Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/75/282d43d6fea7be984ac63e75ffd8eee146ba2f3b077bb1ca5bf3f973e0c4/mypy-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c45485061ba08f700acc12cfc8b177e6997e6bc353605babfc0abb081e079456", size = 14233251, upload-time = "2026-10-28T19:22:44.977Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/a8/9021263cc7e761451c7d8131bdfab3c0a5c2d67aa2d694670019e1ab86a7/mypy-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727890a32f1858c09549eb55a8b234fe990d460450b28f03e8213ef925f1247c", size = 14394953, upload-time = "2026-10-28T19:21:29.492Z" },
+ { url = "https://files.pythonhosted.org/packages/16/10/4192a5cea09ebbc41c534f57bd0d8f0fb42a8e810632cda6e37f072c59fd/mypy-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:beb254fec368c4e7f7f82a5f310c6470b893ec88a90b66426da971c6826b0453", size = 15442824, upload-time = "2026-10-28T19:22:42.068Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e6/d3b0a7474aa4f696594a47a218b6796d9c4346fc81ed288904278cddd9bd/mypy-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ec416d5d2ae689e681f5727e328723f18d8b5dc8c903a146b7851dc5727aad8a", size = 15717227, upload-time = "2026-10-28T19:22:54.026Z" },
+ { url = "https://files.pythonhosted.org/packages/48/6a/2ec530dacd4f77ca50d0fa05b854098f2d75e5d18c587d2612b44f10d68c/mypy-2.4.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:0e95ad0bec03ced9a73b42514208abd24c789e67f0e1eba476e276dd7a2dea3a", size = 7898637, upload-time = "2026-10-28T19:21:41.709Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/56/de9d7bad12e3868d5f1f4279aed20b1b568b2c5e166f2d3a05b211dca0b1/mypy-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:51f742d4154824837ca6e92aa81f00eec222700a3c337bd258e5562a3880d75a", size = 11576656, upload-time = "2026-10-28T19:22:13.232Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/12/4fa9883b9b59e5c96ff62845ad019dc47786031c45d67eaccf452cffffd8/mypy-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:f44dd4c5ab22df21206dd926c98381ebdbf5bfd696df37dd38ded2d23143fcb6", size = 11174403, upload-time = "2026-10-28T19:23:01.346Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/f2/827e356fc4f74c60b8da42663336e035b49d948a3c11224b0fc7543086b1/mypy-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2875280687e64740e6ddcef43bd4c5852ad45ff34549740f6aea3f0373870b93", size = 15713427, upload-time = "2026-10-28T19:21:26.757Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/fa/e58a4fcae1d238189eac758036c0921992a86e3e6209190efd96d61fc5d4/mypy-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea0714d2bccaf6d010c970e1d3ba2c9c2add79d2030aad2ef03167d788948276", size = 15938481, upload-time = "2026-10-28T19:22:34.371Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/54/a20f881801b88900923f2be1541a4b438ad0f0883e03836641fdc3d7adf4/mypy-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c6ff0e7ea546943001a91639c9f0c64b60d239268fa5d29e4a6c87f82c2373", size = 17243006, upload-time = "2026-10-28T19:22:39.425Z" },
+ { url = "https://files.pythonhosted.org/packages/de/1c/ff84a6133a6e89c390bff6b25d9bad6598e7891b205f4b30b496906d636d/mypy-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a5b5da107dc2c93fb0a22f7a45dbaea5dc131afc7056b5468d78e819f9a138e6", size = 17557728, upload-time = "2026-10-28T19:21:43.607Z" },
+ { url = "https://files.pythonhosted.org/packages/15/4a/effa47d39b8fadf679c08e7c78559e523f9769a42a75662d2d15b2b9cdd6/mypy-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c3a4d0c47053cc90035dd3a2c321112b593ad42935d0cbfd747288e1564fa8ca", size = 12370826, upload-time = "2026-10-28T19:22:29.779Z" },
+ { url = "https://files.pythonhosted.org/packages/46/9f/b214ececb1a97fbb61ab3dfd48ae3cffd92ae2c148af4c9dc94c6c56772b/mypy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0c5bf267645351887b72903fd2f5fbddcf85234e0e6c4ad94e2fcfb4f0c3f971", size = 11851083, upload-time = "2026-10-28T19:23:11.288Z" },
+ { url = "https://files.pythonhosted.org/packages/57/5b/e44a2edb0b25a9f1429de770e1d0238af23302671dd40c77ac5efeb0c559/mypy-2.4.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:db599d03e7d60d15f2e0a11665c18bc52a88699fcab990fdf8cecd5be8ac801c", size = 14243144, upload-time = "2026-10-28T19:21:10.975Z" },
+ { url = "https://files.pythonhosted.org/packages/59/d4/5c99dc3a28e11d2f0fcbb02df63d2d3e574dedc0f9f69d50901df305a7a6/mypy-2.4.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6c7210e40dd423af7d65cbe428b5906d11dc60659337bdae4e5fdf02ce32ed7", size = 14409316, upload-time = "2026-10-28T19:22:15.515Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/c9/3108b38f4c30eabd80194f5d9771a87e27196c5e0ddb5bf4b99c6ed1e97e/mypy-2.4.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2d4a51db92059eecc2c33b2a5194b28585ebfd917292c015fe87a77dbff6cd2", size = 15445573, upload-time = "2026-10-28T19:22:05.635Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/12/84a53eceaf11c46dfd7931fb6b8902d01fb33787d3a991e81c461458150b/mypy-2.4.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:01b7718552d017f88d1151f3cc9d6556d778a90bd58060a2c3d87d070f9099fb", size = 15732989, upload-time = "2026-10-28T19:23:06.512Z" },
+ { url = "https://files.pythonhosted.org/packages/65/ba/bf017dc2abdf83b2f74e29c43834b7af67dc1f1b20202f02dae60b4781a0/mypy-2.4.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:3467b1d13000812ab85dc2a3834b9379ec77798ca1bcabe434e23fb21fead692", size = 7898932, upload-time = "2026-10-28T19:21:59.366Z" },
+ { url = "https://files.pythonhosted.org/packages/76/05/0c22b1f0390120a8592d31209cb6b9f932b6a0b426fe643e550de15139e3/mypy-2.4.1-cp315-cp315-win_amd64.whl", hash = "sha256:080cf64a60b90a73435542feecf4171904cc5710658165b40c6869115d4aa100", size = 11577577, upload-time = "2026-10-28T19:22:01.302Z" },
+ { url = "https://files.pythonhosted.org/packages/73/fb/ac01e173629697ddac2f11e51efd10347d673f9a7311b03f4aba710ebccd/mypy-2.4.1-cp315-cp315-win_arm64.whl", hash = "sha256:f9118a3f50de264b614861bb11814833a742138ac3c8e2e49a860b6977fb2704", size = 11173540, upload-time = "2026-10-28T19:21:46.444Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/e4/b9b9c8eb09e70927ee65b1456cfed35710b6e4ff0b07f364ceaf2202307c/mypy-2.4.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:9d7dafd56179d296651df9e1844b91c9517c379398f017a2a435d077a28943c1", size = 15706623, upload-time = "2026-10-28T19:22:20.528Z" },
+ { url = "https://files.pythonhosted.org/packages/26/74/1ceb1ca884add0565e618d013dd24333039100dc0fcc42661561f2e1cae5/mypy-2.4.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d923cec43316f0e6736b8298fd00abeaaf348a091c20e7296b3aebceecb63eca", size = 15944095, upload-time = "2026-10-28T19:21:04.779Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/68/27bcf9b4efa22733b6050485ae37845b94c9b8851abe4fc60632d905c32d/mypy-2.4.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6e081b466e88d4343adffe11aff0f02df34b5b9070b4b700053a0b7294c0be1", size = 17223065, upload-time = "2026-10-28T19:23:03.724Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/a3/ed5724b317bf585c29f3c8be2792e97d45925d65a9c8af40ceeb267072e6/mypy-2.4.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:70196470ac300f444a302a738541101893c2c57804078b33690286a7899149e9", size = 17560589, upload-time = "2026-10-28T19:21:34.539Z" },
+ { url = "https://files.pythonhosted.org/packages/42/2f/005185e6ab4752fd6cc2d1a6d1421fe21979770afe2490c00c555f34a913/mypy-2.4.1-cp315-cp315t-win_amd64.whl", hash = "sha256:de95648e0a6d46fa9929b956ed2c87c5dc9933d4d59945e6390e9d753c74e7c1", size = 12353324, upload-time = "2026-10-28T19:22:51.481Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/d6/20420cc9c780087139662534d128c2f3274361f8bb7e9cf0d5495a846ed5/mypy-2.4.1-cp315-cp315t-win_arm64.whl", hash = "sha256:ef8d5be90032261197ac388d945d8ff4f476c95880798a1911760af64ddfe71f", size = 11845599, upload-time = "2026-10-28T19:22:23.136Z" },
+ { url = "https://files.pythonhosted.org/packages/21/5c/6e8010f8f2c4ad817364592d4b3883633417269b913fc9d5174fdeb7e565/mypy-2.4.1-py3-none-any.whl", hash = "sha256:1784648d6ec8c2b5465e3f26241b845a0b3cb4e56c993c64abe16989e7698999", size = 2801286, upload-time = "2026-10-28T19:21:19.516Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/6f/43edf3be2748d0e40974a5ee046a8bb4c9961a4bdd3e7d5c6dcbf32d1af6/mypy_extensions-1.2.0.tar.gz", hash = "sha256:c6e275a07f1c2fc096b62940452d0eaa3f3af7de45d704787866240a7c6d5a51", size = 6566, upload-time = "2026-11-03T16:26:33.692Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b5/99/7b1adac7e97ce7bbda34792dc6e9b2f5cc5cffd1875883f78a818b2a2687/mypy_extensions-1.2.0-py3-none-any.whl", hash = "sha256:993e7a20aa5671eb41a7132cf555af30062da99729601601b9a8382b11cae768", size = 5114, upload-time = "2026-11-03T16:26:32.731Z" },
+]
+
+[[package]]
+name = "openapi-generator-cli"
+version = "7.27.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1e/8a/904a3b7ef01d4e251ce1c9522f6daa063e8939eb4def33dad95cfb0ebb5f/openapi_generator_cli-7.27.0.tar.gz", hash = "sha256:f7b8989353031df78a0f9317a22066a5ec7eb80330c2e6e7ad1288068b0bc756", size = 29690004, upload-time = "2026-11-03T05:40:05.041Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/83/9c8a3ed63a7a518447e0e03728552c2a939342383c9aea0a6950899be3dc/openapi_generator_cli-7.27.0-py3-none-any.whl", hash = "sha256:0e5b8b2894ebcd775250b1b1b6cae82d6405532dff74ab66b68766edfa59a576", size = 29704286, upload-time = "2026-11-03T05:40:01.277Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
+]
+
+[[package]]
+name = "perigon"
+version = "2.0.0"
+source = { editable = "." }
+dependencies = [
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "pydantic-core" },
+ { name = "python-dateutil" },
+ { name = "typing-extensions" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "mypy" },
+ { name = "openapi-generator-cli" },
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "pytest-cov" },
+ { name = "respx" },
+ { name = "ruff" },
+ { name = "types-python-dateutil" },
+ { name = "wcmatch" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "httpx", specifier = ">=0.28.1" },
+ { name = "pydantic", specifier = ">=2.11.3" },
+ { name = "pydantic-core", specifier = ">=2.50.4" },
+ { name = "python-dateutil", specifier = ">=2.8.2" },
+ { name = "typing-extensions", specifier = ">=4.7.1" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "mypy", specifier = ">=1.5" },
+ { name = "openapi-generator-cli", specifier = "==7.27.0" },
+ { name = "pytest", specifier = ">=9.0.3" },
+ { name = "pytest-asyncio", specifier = ">=1.3.0" },
+ { name = "pytest-cov", specifier = ">=2.8.1" },
+ { name = "respx", specifier = ">=0.23.1" },
+ { name = "ruff", specifier = ">=0.17.3" },
+ { name = "types-python-dateutil", specifier = ">=2.8.19.14" },
+ { name = "wcmatch", specifier = ">=12.0" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b4/fc/1203d011145c98ddce3cd2f505bb54375ef8d43223053df6bfcf3c746f46/pydantic-2.14.3.tar.gz", hash = "sha256:1054f237f4d4733e5a30e1e2333bd33a2004ab9ab1510bd053a9a31bb48e232d", size = 925636, upload-time = "2026-11-05T09:17:04.686Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/72/b8/b864503789ef5c58597d2a7b4adbee62c26440ab1e682581fc6838df8947/pydantic-2.14.3-py3-none-any.whl", hash = "sha256:3f7b347782ab7e4cf4c798496ae3a82787d9f21ec12676a9c564355eeef8daa8", size = 494071, upload-time = "2026-11-05T09:17:02.426Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.50.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9c/0f/34f9ce4ba701dd0b815033ef94d99b56f66200e578ee6052aa3ac26f064e/pydantic_core-2.50.4.tar.gz", hash = "sha256:aead3b74b68396bf0feab5d57e050b730ded7df55853c1e048a87e00aaf033c6", size = 522249, upload-time = "2026-11-05T09:15:26.774Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/00/85e80811834d7cbd9bc228f9b5cdf8ec0edd69b55351ad720aac8c412d8c/pydantic_core-2.50.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3e547ceec1da7382f01f9e13963023e53367a71802c88778b21b9645a077cbdf", size = 2170584, upload-time = "2026-11-05T09:11:41.139Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/f1/438de1fb99d717dcc6521922478dc16ef7b757302d4fc1f7faba3aaeac09/pydantic_core-2.50.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c813ada7a651193acda85b6bc1cf12abd4b37cd25c7c6a75d7183512cc374969", size = 2128471, upload-time = "2026-11-05T09:11:42.65Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/74/f21ab568c6d3f315988b40e53f809c95b63fafc5403332963aa6dfc2c054/pydantic_core-2.50.4-cp311-cp311-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:57fa0a9490785139f1a4e4f43891830d55dfe4408845c0c9cb5eff4aeaf75f69", size = 2281509, upload-time = "2026-11-05T09:11:44.359Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/42/24ed3ea8e2fb6463c9ba5b8b1f089e69040905c7fd81dcfa6f52fcc692d5/pydantic_core-2.50.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30d4dc35bc0eea52202a90b3820445ee0072c820241bfa710a623a078c9b267b", size = 2128064, upload-time = "2026-11-05T09:11:46.01Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/d1/44f541b2c18a46df6f18281a557acefeb5bbf0733a0b80b0a5e835e10ee4/pydantic_core-2.50.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:789f88dea57841cdebef392b5d6e90444d767117e9ff2e0d9172df5c9de6690c", size = 2214472, upload-time = "2026-11-05T09:11:47.503Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/1b/e9a9ad8733e8051abca9e4acce37ba3ddd13930fd3b8730f4c7816fa979a/pydantic_core-2.50.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d690121e80ab16e623415351ab24713534d4e500b3cadff216409cb6826063d4", size = 2401153, upload-time = "2026-11-05T09:11:49.403Z" },
+ { url = "https://files.pythonhosted.org/packages/79/84/d88e8e6aa42d4d677666d6625db9177ad6851be9e7564a25aeb0b0cbcaf2/pydantic_core-2.50.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b42f64825f5d750228f08318a9bf4d723b7ecb4c472aedcb3013c3ec2dc1f949", size = 2453307, upload-time = "2026-11-05T09:11:51.118Z" },
+ { url = "https://files.pythonhosted.org/packages/56/51/cbb7a1bc0196ff94d3478d265eed7449f811759988eb760d41f4edd0893b/pydantic_core-2.50.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0574866e05f6adeecc520d87e87dd06210c0a51706341f6ef8421572f5a64444", size = 2253434, upload-time = "2026-11-05T09:11:52.997Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/04/206b4881d631033ae88f005da6e11bd610c850e1850217342c03053d253b/pydantic_core-2.50.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:dcf2c7eaaa4cafe8e49b017e5bb49e76e084ab6f0cd712ee4019c812b5cf68b8", size = 2279365, upload-time = "2026-11-05T09:11:54.822Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/7c/d815b5a3f83069b283d3b6a2e61de6e14baac3b20c6cd3418e9e629551cf/pydantic_core-2.50.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b8ee2589a082739c522c1fe4be227412fcf108b903abb6672ae2f5570558882", size = 2319407, upload-time = "2026-11-05T09:11:56.46Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/00/c9a483276d1052d5142e1e01eccac97d939f83a3a3fd94c4766495a9b807/pydantic_core-2.50.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:aec3c2d741a8e3ee993de7f95f37afb51e31b169c95dbd29da4cf6688f340914", size = 2489900, upload-time = "2026-11-05T09:11:58.102Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/b5/8cd8ee29a1d63ad47f95593384df92476cef6896c644f280db0748b8e76b/pydantic_core-2.50.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8923fcd49f875e8a0448d7223d1fdf1a9973ae2d1f20d4158753ceb8909facd2", size = 2497732, upload-time = "2026-11-05T09:11:59.928Z" },
+ { url = "https://files.pythonhosted.org/packages/60/31/050a48c0c373df16442ac07f3fa167414bb0e22f54a3e886f6d6cb7ad1d4/pydantic_core-2.50.4-cp311-cp311-win32.whl", hash = "sha256:fc04affef30be98c1f36f0c422a4061ef618fdc1dde79f5ad5458dd496fecffa", size = 2132347, upload-time = "2026-11-05T09:12:01.928Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/06/cebeeb7a8e87b42a3d8bc189d0d400e47df5d1736cb326764bae8a74c433/pydantic_core-2.50.4-cp311-cp311-win_amd64.whl", hash = "sha256:15bece43a02ad543c621c8305e67dedb5f27d378c5ba741e5a2c36523f8ef37f", size = 2255970, upload-time = "2026-11-05T09:12:03.888Z" },
+ { url = "https://files.pythonhosted.org/packages/46/db/5b4bf14347da658c994483043fbabffedcab98929c41c261ec168c3c1f87/pydantic_core-2.50.4-cp311-cp311-win_arm64.whl", hash = "sha256:e498900bfbf7ddddd862f810440c09e9b81674739e8a11d619b40906dccd4a9a", size = 2196799, upload-time = "2026-11-05T09:12:05.389Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/d1f6b81709538f831ebe1c42f50fb89db3dfda06f9e9b1ea24e90fc69209/pydantic_core-2.50.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:32a991aebd165f227b7c7c60bb9d66c2c7de9c24308552da264c9938fde0f8d6", size = 2231444, upload-time = "2026-11-05T09:12:06.969Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/39/6618e541658d5a687a8d4962fc304350073a8f322bc4c62e8ed4c2d514d6/pydantic_core-2.50.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e20cbde2b6deb33a51d08cd168454dd9044ec7d4474056f4d98d63f51578783", size = 2108048, upload-time = "2026-11-05T09:12:08.85Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/54/1c941fffa4e14bfc9ab0cd4977075f0e03201447856130d08a33cd45e47b/pydantic_core-2.50.4-cp312-cp312-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:8cceae48ae7f94990c2f4e36291e9ec9868735e8b0f19e4489a17b0422338466", size = 2256866, upload-time = "2026-11-05T09:12:10.495Z" },
+ { url = "https://files.pythonhosted.org/packages/61/a0/8028ef3e014be522ce43060aeac82a5be2288215e660866f5e6260acc123/pydantic_core-2.50.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16902a147b3da4840612164a5e67947104d63e978f7a57c395877a5e26690b9c", size = 2125682, upload-time = "2026-11-05T09:12:12.278Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f2/b98bb73b1734ec4e42e786a766abc24f3c89620a0ddbaf8aa14e4b4c628c/pydantic_core-2.50.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:d6e3485a5e5c01fb5339c1ee3d4b9e21e2135a2a9d44caafb71acd994487ee7c", size = 2197108, upload-time = "2026-11-05T09:12:13.873Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/d1/7a7142786781400143e8806633fe5b65e2d407c311041fc9494490f05b18/pydantic_core-2.50.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:629eae995185540406e3635df5d5cf1d44f19ea240840b835d3f44541ff694d8", size = 2380521, upload-time = "2026-11-05T09:12:15.539Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/34/cf710ca48d4498b04a3ea605d45892675dc2b14a94a25121a8d03f56d3e9/pydantic_core-2.50.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:41b2cd8b29dbb5a4c9828a70f50d1abfc92011115c813213d3a91205c6f731b3", size = 2457436, upload-time = "2026-11-05T09:12:17.447Z" },
+ { url = "https://files.pythonhosted.org/packages/91/23/e0c6e3cd09a0ac809953fea698c816b9f03bf0b35b6820c9553e4f5d85a8/pydantic_core-2.50.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c81d98e27d9d9261909dcccab1ba5d6ea1c98b0bb9470e368604614fd1d36ddd", size = 2261727, upload-time = "2026-11-05T09:12:19.097Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/00/292a0ed96d5d2d6edb49468ef481336e5b7e54f13435d73b7c912c891c23/pydantic_core-2.50.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ca83a32b2b41653d89f09a3d22b770bca96da72273bef8f7a68136bdd1e8b823", size = 2262071, upload-time = "2026-11-05T09:12:20.78Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/36/d4690faf8c082eacf7754e52ab6bebe1bc73b167272e28699c00480ee908/pydantic_core-2.50.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:817953730e4a73a09008b010c4a42396cefe799616a22df2ac21d3927b3080cf", size = 2298931, upload-time = "2026-11-05T09:12:22.614Z" },
+ { url = "https://files.pythonhosted.org/packages/21/17/b180af6d17cf6c8e0d3d2661fc501a4a40959744ca4947d6ad0db81b1416/pydantic_core-2.50.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:664e74848c2f5fc27db8505fe24e1ecd3ac669412913bf73d176916f86cc08e2", size = 2474285, upload-time = "2026-11-05T09:12:24.842Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/bb/7e44d4ac6e1292f1859384c688713045bdf5448e0635c49c003d2be3bb6c/pydantic_core-2.50.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:219114f8dcdcbe4720d82ef016b1287ce5a0a67d7c69ca61524eb72e82aac344", size = 2498172, upload-time = "2026-11-05T09:12:26.434Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/84/5101f35294579c4bef7dc32fd4f097d91eedac2e195894266bdabc1e6dfd/pydantic_core-2.50.4-cp312-cp312-win32.whl", hash = "sha256:67f7cc397e1d5556a801d1cba74de0d27d827eb242e6c036c0ca273c745414f1", size = 2119592, upload-time = "2026-11-05T09:12:27.971Z" },
+ { url = "https://files.pythonhosted.org/packages/82/88/d438a8fcf3738e9ff2670f724047ca3893ec899bcd5cdfc077a2aab133c6/pydantic_core-2.50.4-cp312-cp312-win_amd64.whl", hash = "sha256:ea2f61b0bc12f3592288503961b764579dea0658852d96a0b3d823614d808821", size = 2264064, upload-time = "2026-11-05T09:12:29.538Z" },
+ { url = "https://files.pythonhosted.org/packages/77/d4/4a0c73dc3ef9e50b122b8c3527afcded15a44838f1c9668d2d4b72cc09f0/pydantic_core-2.50.4-cp312-cp312-win_arm64.whl", hash = "sha256:32d6dda86383645ec428ea64dfc277036f5cbc60a72238eab28b91a3fdb9a113", size = 2167792, upload-time = "2026-11-05T09:12:31.103Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/21/39f8d97e7ea8e3083606ee9bb1d7f3ec70ded780bb6a872aad76c7eb08b2/pydantic_core-2.50.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0f87ae9db5ba8d8de40dcb93982cf8dd544e6552a80d3345af51941cc2146b80", size = 2232841, upload-time = "2026-11-05T09:12:32.617Z" },
+ { url = "https://files.pythonhosted.org/packages/90/a9/fc89a632331882c4cb91b949003418e6f5085ddcf2c9b875d499682bca91/pydantic_core-2.50.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1239daaf063e62213ca1f34d96663900bd7510c42a786e843fd328af70e46445", size = 2108326, upload-time = "2026-11-05T09:12:34.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/6d/4f6d00e9e4ba2a7bfb206ae49cb32a3ea90c4d658e62f8775277d75e8291/pydantic_core-2.50.4-cp313-cp313-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:1afefe44ec042fbabfb4483ea258c04cf75ff4ff328d754f09b321fdaa232aca", size = 2256276, upload-time = "2026-11-05T09:12:36.212Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/b8/936cb0d85a4082e902c7397db0f05c4372eac08372c8bd2d0b43088fcf17/pydantic_core-2.50.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9ee990bf2cfbd81574c5d4f9f6c9d480a14b5b43d2c4f2ca13f4c6f48089394", size = 2125469, upload-time = "2026-11-05T09:12:37.913Z" },
+ { url = "https://files.pythonhosted.org/packages/66/a1/f3b551ce233dcac73286b8b2ae45c23232a9f41c68663e2dcce4bb2dbcfa/pydantic_core-2.50.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:ab2822bcdcefb54d1e8ca29f5e3ec3afe160fb794bb3a3a73b09d11ed2671104", size = 2196397, upload-time = "2026-11-05T09:12:39.725Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/93/a8a6e7e20826567a73e6f52416667698aa3de704804d7a49d25272246af4/pydantic_core-2.50.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0001d6920e3a5d92758cda460d6dc9f918a78ad6d5d092f1c8b5395e6b775356", size = 2380442, upload-time = "2026-11-05T09:12:41.196Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/ba/921d90c9d8ddfa653a35a92f2bbdcd650a9493f5c43f39cdfe4c56659cfa/pydantic_core-2.50.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ad92d255a165cde5f9882ff69699eaf5495e0a1f32e97566bbbb80f11e46781f", size = 2457747, upload-time = "2026-11-05T09:12:42.723Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/1c8f2c8f1b03d3fe2fdd7962df8f5a744fe48c90b4c49615e4eb77d04141/pydantic_core-2.50.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb2bb5b557080da398b866408117d98fe9731a12334afd0cdff221b458cdc5dc", size = 2260946, upload-time = "2026-11-05T09:12:44.185Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ab/5b78a78f0c0f11c8b54fac214ed49cbb4795c34881aa5e5a15715917936f/pydantic_core-2.50.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:92f8f63df729061bb14558ed740e7a222f6e2aa6c2e6a5bcfb4e706676562049", size = 2261921, upload-time = "2026-11-05T09:12:45.706Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/e8/dd5d154cf3401f836d8754fdc1b82ecadd37944e4355d855e5b54c7d8ae5/pydantic_core-2.50.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a60ec2a57ac3ef3767e526e9bf706ec75aa3d79c4ea134d9d4fcf7c62a297a7", size = 2298475, upload-time = "2026-11-05T09:12:47.183Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/49/af059d9c4483cba168b17e5f84f1090a3e4547a02fc7698ebc231d48298a/pydantic_core-2.50.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a2f8716344bbbd10443391be0b1f2003b34949e3c4e6883005c2bc498ab6b64b", size = 2473226, upload-time = "2026-11-05T09:12:48.785Z" },
+ { url = "https://files.pythonhosted.org/packages/71/ba/4e8cc3064e8bc2ea272cf80dfc57fa4e455f6835c1def21609a9b4f94135/pydantic_core-2.50.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b300a30304c4c6aeab03ed06809819d9eceacdd58441d3c7dffe41edfd006091", size = 2498764, upload-time = "2026-11-05T09:12:50.316Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/96/0878499cfa88eb0b07762b488d3cc1b64cb156a4905db99a0d12096b2e17/pydantic_core-2.50.4-cp313-cp313-win32.whl", hash = "sha256:ef8a9ddb2e2a4eae4abb72dc0710eca6c2726779b0664bd10c3ce9fcfaed8c4e", size = 2119158, upload-time = "2026-11-05T09:12:51.834Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/e0/95b651213e79ffe0cbee855590bf5cfdf8e3a0d90a7fc73a080e63f7cc38/pydantic_core-2.50.4-cp313-cp313-win_amd64.whl", hash = "sha256:4c29b8f2d6417069bb1f257e6b9113f5d64dc2f554cc200ea90b4fe2c5cdb733", size = 2262887, upload-time = "2026-11-05T09:12:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/08/d3/c3801dc75844a0b13d000bd575bf1b3b3a8aca9f63d19e1809f69c9c5121/pydantic_core-2.50.4-cp313-cp313-win_arm64.whl", hash = "sha256:a0c5210d038418a115e7cf8706bfa611bd14add2f5787274dba45aadca3155e0", size = 2167878, upload-time = "2026-11-05T09:12:54.677Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/ed/c9a4982ac3bbc78f27498d9efb85b3dabd739a1724db53c35cc4c018e1de/pydantic_core-2.50.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5c074454dc6d4f6f4ba6561a56bf33a14db7651d882a5a5321ba3ae8868e082d", size = 2236841, upload-time = "2026-11-05T09:12:56.111Z" },
+ { url = "https://files.pythonhosted.org/packages/56/32/92c174c17bfc5fc52d0c064e6a7969a727731e25829ede752fba8bacce0f/pydantic_core-2.50.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4bb174bd65f795c39ad6642d9d5bdc43351b306b7d0a654ab0d0abb38be245f", size = 2108348, upload-time = "2026-11-05T09:12:57.638Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/cb/9f637d424a04d3edce7dd527da941950cce05a74b025eef3d54427b86c8e/pydantic_core-2.50.4-cp314-cp314-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:0ec20aca2b067369913716847f7dfe5f4dddf16a46965b7a70bbad5defaaba86", size = 2261331, upload-time = "2026-11-05T09:12:59.154Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ff/1117a3cccabb67305bd98d992a84eaf275939a9a043b4f14126ed7252e4f/pydantic_core-2.50.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:70dbc9f83c41f37a4faa62dae5203853847223a891da446f48ef2efcfd5df806", size = 2128667, upload-time = "2026-11-05T09:13:00.614Z" },
+ { url = "https://files.pythonhosted.org/packages/93/45/6de1b4a6e8c415cd547f90d1408a711e7975116423e3eb3046061c62d7c7/pydantic_core-2.50.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:3de6c2a4df2af0ac9ab4dc1488746fe8b1a90e27955d870d9edda3187d20092b", size = 2198183, upload-time = "2026-11-05T09:13:02.259Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/bd/c6e64c315d35cc0783a0ea033a3dca062bc368905d0217eab6f6f63007de/pydantic_core-2.50.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3a48a5254d15a0ad050318bfc7a9566d4ec740118187e8f303e47a6840287eb7", size = 2386279, upload-time = "2026-11-05T09:13:03.988Z" },
+ { url = "https://files.pythonhosted.org/packages/33/ab/0d56280c89d3841381e9c699c3fa80e3dd3288edf5e7fe4d149136835dca/pydantic_core-2.50.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:0af56dbf8822ba9f4efcbbfdc15478f8b6fb08f1609860cb50aa2cc8476b3c34", size = 2464343, upload-time = "2026-11-05T09:13:05.98Z" },
+ { url = "https://files.pythonhosted.org/packages/56/9c/621ad8f88590a92a086b1168dce09369d9c5e079e2e635e6646d248af55b/pydantic_core-2.50.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1d62fb172db35c6478c9323da34ff37d1f3d37d7d44337a9d94ba8c583e5730", size = 2265388, upload-time = "2026-11-05T09:13:07.808Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/7b/cef68a667bf29b86e293a80a601d488a84c8f45508915ad6201fcfe1a798/pydantic_core-2.50.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:5c1013647be741647895c102831dcf79c34209662dbb683df9efb864417ed7d5", size = 2267012, upload-time = "2026-11-05T09:13:09.273Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9f/3ae4d163d8feda2b52de88b5cd96b1c40f79d11503003b061136d2ece6ab/pydantic_core-2.50.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aeb2e123cbf978a905e9f55155c1b8316fbbfa7cf64d04521fe174bab302d100", size = 2300478, upload-time = "2026-11-05T09:13:10.758Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/41881d31f6452430ed6792679c5391795910f0a27a6aebd424683e504ed6/pydantic_core-2.50.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:523466eba8ee10f7480f3c2057563145978011c583130d2cc01fb1e7af808e25", size = 2474874, upload-time = "2026-11-05T09:13:12.188Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ef/099874dd6d5ca1e49987dea87eecc05610183249450b1fff883d87f1e8eb/pydantic_core-2.50.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:c9e70012926e97069cdd09221861aff412aa94e4cac2e019055cdb79485b150c", size = 2502771, upload-time = "2026-11-05T09:13:13.981Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/07/a7d8615b8677bf69e24666bbce431299850c95a7b084649342582a1c3210/pydantic_core-2.50.4-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:e6da138a1ecc1341c8b788a5ee822670b03788e1f5decf7af9d461de6e508b8e", size = 1496509, upload-time = "2026-11-05T09:13:15.704Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/71/fdf2c25c3a4f89968263c1d896c933809ca8d2fb13443626648a17b2e296/pydantic_core-2.50.4-cp314-cp314-win32.whl", hash = "sha256:13b6dd62b5279f5b9d72f3bf918a35443ee8bd549241d9dc52d79a2c3b6d7016", size = 2122015, upload-time = "2026-11-05T09:13:17.09Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/9f/3554f0148b01ddaa5e678bf5fbce4679873534e5c0985cdf6c59c41dc9a9/pydantic_core-2.50.4-cp314-cp314-win_amd64.whl", hash = "sha256:b0cdb655fbd1562c2dfc9986f9103a106f7058cd0915afc6e656d4b1d73f3387", size = 2265109, upload-time = "2026-11-05T09:13:18.702Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/3c/196ef237d9ec5cca5ea9b773911dee1a387b9b1d4bfea05625b3e191a338/pydantic_core-2.50.4-cp314-cp314-win_arm64.whl", hash = "sha256:df1c174796cd63eb6b139f7daf783affe1f3ed659605850e843b24764c21bfc5", size = 2165745, upload-time = "2026-11-05T09:13:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/36/12/0ee82a41a3995f0b531cf14e0b823dc2365ba4a22330670fccaa72753f12/pydantic_core-2.50.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:edbca8789fd10f0de534b660a6b4c71f1fd18039679c918dad392ab044bb37e4", size = 2223917, upload-time = "2026-11-05T09:13:21.623Z" },
+ { url = "https://files.pythonhosted.org/packages/90/2c/592f8404abbcc65a70e6a802d1dc8f11412af44913e5146cf5c345370074/pydantic_core-2.50.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:33b21f69540a12c2053cd1d95766d8bd9667a3897f7c712a642de8aa37eb0865", size = 2087372, upload-time = "2026-11-05T09:13:23.16Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/1d/c7111757c84b5b8c19b1e39ceb5e131e6e48e14169ec6004e0f8f28f43f3/pydantic_core-2.50.4-cp314-cp314t-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:ffb616d8d55965839670301dc3bae3abacff560a7e137a89056058a8a9da4d96", size = 2275190, upload-time = "2026-11-05T09:13:24.634Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ae/6c49cc9b8e66e61adee782d1763894bf64a357cddae82f725059b85bdc98/pydantic_core-2.50.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c5687b59b6ba0f663c4f2f15470f68507d6b45ccd3a19ee6d224f95cbee70d8", size = 2110343, upload-time = "2026-11-05T09:13:26.168Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/39/0ee5261d1daa9ad526371181c13696add0565251b2823f6e00a9a9af397d/pydantic_core-2.50.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c733087ecf7dde06b1d071679bfd5f3ba03266ba77814c1c7253bc48c9adedd9", size = 2207887, upload-time = "2026-11-05T09:13:27.681Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/b5/a0652c0e99590107e111088b6d059044380a787da9ffdd0b458ab4cd9453/pydantic_core-2.50.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94733cb447c37b0ce6f71b39ca1288d5f966a084023a5617b4cc35a852586859", size = 2404171, upload-time = "2026-11-05T09:13:29.864Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/85/d49f0b8e09a0ffc40dcefb604f9e6020e0d61b51a5f64a504f382fa84c39/pydantic_core-2.50.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b26f39a5be3cd91a2035818be3d0ffdf5282c3e1f5f53d3b80b7024021acee52", size = 2466049, upload-time = "2026-11-05T09:13:31.658Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/76/f2b04d7c53edfd8136053c812ea49914fbf4091196cb7baf9ea326fa2ff8/pydantic_core-2.50.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c856e439a9d54d514e81a7131229ba151cdf81df7c408e0f6c89340e5003dfe2", size = 2262062, upload-time = "2026-11-05T09:13:33.806Z" },
+ { url = "https://files.pythonhosted.org/packages/24/60/016f14cea883adc33df90b8df220df934de49ded9640f45c8e92bb298494/pydantic_core-2.50.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:cbf0777d54672990b10cb8ca9b0523499e182d7e2ea39f86b138519a902aa601", size = 2263713, upload-time = "2026-11-05T09:13:35.337Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/3f/b05828e978512973e7e215bab69e65e70d223d6309a17bf48084a50692f4/pydantic_core-2.50.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:edc17549ab592e33cfcaf285f047bb75fcd98f608aa9309af1187fd736ce9641", size = 2305044, upload-time = "2026-11-05T09:13:37.043Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c1/fca6c5f623ac45ba284713c614b30173aa0794899a0a457f866fa6f5e886/pydantic_core-2.50.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:23a9ba564b2db709ff16eca990992ba95a19069972b34bff87c81d4b473cd755", size = 2483506, upload-time = "2026-11-05T09:13:40.524Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c2/ec41f68d1208e53e1a73ead940c050c4316bbb555ba42676a5e949fd529e/pydantic_core-2.50.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:41f4f8ae714e4fea1d635880f8a1af715ecf080eed4d13c5b04a0f3210f9c61a", size = 2512832, upload-time = "2026-11-05T09:13:43.361Z" },
+ { url = "https://files.pythonhosted.org/packages/43/22/58583b86309f70cce854e0e77c412839a56ac6c682d6060f5794afeff315/pydantic_core-2.50.4-cp314-cp314t-win32.whl", hash = "sha256:601fce6cea9ddeaa85329e12e7f351def38d233075f42a3012b9c48071b02941", size = 2101977, upload-time = "2026-11-05T09:13:45.735Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/28/193bc0994f7fe15f2874c1dfe2f3bc7872aed8283ffda2cf05fb9a5a06b4/pydantic_core-2.50.4-cp314-cp314t-win_amd64.whl", hash = "sha256:22e874666bdbd96d6bf629d32cd8ab376fedf1fd62585b52a42b9fa9fa6c20b6", size = 2240461, upload-time = "2026-11-05T09:13:48.38Z" },
+ { url = "https://files.pythonhosted.org/packages/00/ed/5615cfdd5620a4926440eb1a2b73db0dc895824ba565ccaa8d9eb3a8e974/pydantic_core-2.50.4-cp314-cp314t-win_arm64.whl", hash = "sha256:71395a43da5584bb70556f380387e15f845c0dcf76373c1afbfa0025737576ac", size = 2168505, upload-time = "2026-11-05T09:13:50.762Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/9f/e6673535165d965a11c1d5339f53995f90234019827eaecb41a57e6e463d/pydantic_core-2.50.4-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:7ac2769a7ddd661e07e26c12220adc31b17014c48234b2a5a88993b8d4109e09", size = 2241950, upload-time = "2026-11-05T09:13:53.055Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/02/65951efadbf7826e6a270843f97e3da2ef726de2b6d9cd7c5dfa4c48807e/pydantic_core-2.50.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:930ad463d3fb7a340d323bcc496ec32dae469ec9974c29ff569f19806b6b03b4", size = 2112412, upload-time = "2026-11-05T09:13:55.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d3/4fb6e2e4a9e4951f1e05b198366ed5330e0874e83d52077e8b985350fe0e/pydantic_core-2.50.4-cp315-cp315-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:b867f9fd183d76977f810c626d539265183c25b2284762893a31e8d6cacc81c0", size = 2263220, upload-time = "2026-11-05T09:13:57.555Z" },
+ { url = "https://files.pythonhosted.org/packages/63/83/42e70436c523357b49393b73f161f2741acf36003c8efd2f941d2be56bae/pydantic_core-2.50.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49eb1241a969a5994db6880c18c012b6cbe2340343d8f529721571806583e500", size = 2134716, upload-time = "2026-11-05T09:13:59.413Z" },
+ { url = "https://files.pythonhosted.org/packages/36/66/ed9b87f4751fd07e472c62cfe151f575abdf4c452fab943d35ba8efdc6c4/pydantic_core-2.50.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:477af785d1846e4e96bf56983b92ea7553aaeccdeb8acdaa55c7cee9eda0ae32", size = 2201901, upload-time = "2026-11-05T09:14:01.472Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/21/ff2e6ac7202b6d326e9f007c6082f6e6f3aaa063790ea2a355bdc985addc/pydantic_core-2.50.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:29358a8ca74e7d81158d14e2b10c904bd18773293ae3788716c0626fe0342347", size = 2389670, upload-time = "2026-11-05T09:14:03.431Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/34/e9348bc13d549641791a51843ed140fbb311d9066628e27477343412d5ad/pydantic_core-2.50.4-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:300753f71c73302e218124baf995f00058e085cd01869bd9610bcbd0bd4500c1", size = 2468951, upload-time = "2026-11-05T09:14:05.028Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5e/145a02bf2b9f66981108caf6cc9e78917e31e4caca14d1e3ccd3ff2c7293/pydantic_core-2.50.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:26cb012c4b8ca9f847bd0ac15978bd3d0f55b050bfbe6c47355edfc7e945029b", size = 2272957, upload-time = "2026-11-05T09:14:06.587Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/38/b71c5b245e923f6cf144e948ed2a9d6b7fb97d8905784db8d860f5176b8b/pydantic_core-2.50.4-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:9be1cfe715ecaa060ffd5d599ef410b94ef2697a0eed4cee562f788a2aa2175b", size = 2275028, upload-time = "2026-11-05T09:14:08.219Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/71/2504dacdc5cd3ea3ec5e68c9b6e097c979500bcff220b97046cc8ea3c1ad/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:c81e35862aced259177a537ce1a6264c3a97bae1ced184ca9dc10ea754563688", size = 2305447, upload-time = "2026-11-05T09:14:09.795Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/90/904233a6c148bcac9f30bb96a2761e5fb52682d0d6de31a40c74a3714ceb/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:b2f2d13239487cecaae98c9f4d5904ad0737643cdf8873c88ec6a78b12214f20", size = 2477852, upload-time = "2026-11-05T09:14:11.443Z" },
+ { url = "https://files.pythonhosted.org/packages/72/33/1cbdcbf343ca23b507e967b21dddc4c8bf17a74c8b5cad59228c85ba4efc/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:e6b4b08d09054310a4dc389fa61ee22a7a5a292cb23ab8a985136ca8828bf924", size = 2506352, upload-time = "2026-11-05T09:14:13.165Z" },
+ { url = "https://files.pythonhosted.org/packages/91/24/3ad9ea0876e6624c90967e5f1446e751b275cfdfaaf798a0d8e0b30025bc/pydantic_core-2.50.4-cp315-cp315-win32.whl", hash = "sha256:6619d49a89a15cd04334f76fec530c155923bfc60da74ff5d815a52629e1aa6c", size = 2127780, upload-time = "2026-11-05T09:14:17.52Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3c/0582532b90a12492471977bdbca1317eca5144f6342ea581d2cee73ccd35/pydantic_core-2.50.4-cp315-cp315-win_amd64.whl", hash = "sha256:e5bd109d095aaae989a0463d5ef0fcc43e9ddd5de125e656d3c119a79e466380", size = 2270596, upload-time = "2026-11-05T09:14:20.955Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/50/967cfe4f7043a993c0c80cd3563c598b5281d2276aa775d0f6ff7f714271/pydantic_core-2.50.4-cp315-cp315-win_arm64.whl", hash = "sha256:6118e5a5550e36582e19bac1a403711a748f9f18251b054c326d3db02aaa7a18", size = 2170880, upload-time = "2026-11-05T09:14:24.822Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/71/3ec5bd8a82f792482f16d79fdbe8ce85aa71bf7147e556c5fc5f8546818d/pydantic_core-2.50.4-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:d8201c9f9035520cdcc7dcad383b039ec8123969b87e01cf27c3dd883c02313b", size = 2233053, upload-time = "2026-11-05T09:14:27.942Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/2e/614dc37aedf8e9f811c1f2eedaf8eb09a7c196e2d137fe82bcc9d5662ca2/pydantic_core-2.50.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1de7be9a66fc8536770481ab64a05aae5067c725f2b29a09543af00e6a417b97", size = 2090776, upload-time = "2026-11-05T09:14:30.281Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/14/aefc14e3e05013070ee959fc0b8703f6860a2776ed6e75b9c748f61aced0/pydantic_core-2.50.4-cp315-cp315t-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:789a6453e5ffd18a7ab6b8c3a0dd8403053829197ec258ecb90c604c21ffcc99", size = 2272481, upload-time = "2026-11-05T09:14:32.578Z" },
+ { url = "https://files.pythonhosted.org/packages/13/c9/d56999aa7236dc9303091db2b1ef7303f0b022baf25ebf33cd200c0cb933/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6348ad12fa741bcc9c2f173e99fbf27cbc038826d8c31a4a7d836eca27878ba2", size = 2113069, upload-time = "2026-11-05T09:14:34.69Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/d5/a62da9e72f272f198f6118c5457332bbd4b9fdc4b77858448120569756de/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:d49127f137f871cde3c5650af33bbbdcee02679dae76e020902fa106f522b8ae", size = 2202754, upload-time = "2026-11-05T09:14:36.49Z" },
+ { url = "https://files.pythonhosted.org/packages/37/4a/e7f21577f96e42ac3082cd014df398c1c913d482afac1c8ef17b9e039198/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2f5cc5bcb94063fbbdfd285a2b302da34af07491337c10bb35de1d5220fa1583", size = 2411472, upload-time = "2026-11-05T09:14:38.63Z" },
+ { url = "https://files.pythonhosted.org/packages/38/ad/c57c5e13bbfd3404ec1af519764aadbc52ee01e39f9f9ae9d28317f982f2/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b99bed995ed7c41515c11fe2beb86f8a6cc298e5537cae2f736de2f982f74ef0", size = 2475330, upload-time = "2026-11-05T09:14:40.519Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/63/65cf0e1f1fd8cfaafee3c005712b8020e063b0f3e3571493ea68db5ec44f/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:faa4e96385c820ce9d7f7a623486e23d4134ae3c9ba2071e44e0ab5a5a5002c4", size = 2265718, upload-time = "2026-11-05T09:14:42.379Z" },
+ { url = "https://files.pythonhosted.org/packages/72/5d/fd9fdec9b25e2ef1f788970a84f749f0e28f166573c87654fd5387d8e5ce/pydantic_core-2.50.4-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:d6a166978f22f10cb91413f70519861605219346decf65bc79008f9257c94afe", size = 2273454, upload-time = "2026-11-05T09:14:44.286Z" },
+ { url = "https://files.pythonhosted.org/packages/34/8c/19705c6d5eb72ff8c25006df8a375da7466176a5968781789e5415384f65/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:a70d5effebd81ebd8e15a8aabdc7980832367b2b00c36543bac378977004cc80", size = 2314862, upload-time = "2026-11-05T09:14:45.97Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/fb/acefa27031aba2646b0d3026c4a1602e526489d39395ab17bc47f03234e3/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:f8fd23653a252cefabea11cd041efd7b80bdf403e43a36dea53f9cb0aaa21f0f", size = 2479073, upload-time = "2026-11-05T09:14:47.526Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e1/db08fa1e5edf1779fa0612f8d09f9030bdc513f593687c883cb781b86caa/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:7646353a5a7c553c04167aca628e2550b538f492765b03ccf34321a560acc43c", size = 2521328, upload-time = "2026-11-05T09:14:49.042Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/51/bf1ec5b96c076d8d514b08f3bcffb3397df7a0fcc729eb960838a8bbe27f/pydantic_core-2.50.4-cp315-cp315t-win32.whl", hash = "sha256:65376dbe1ab4dbc768ca2b72b7c13993562c035f5925fd3bc7ff9c8c585c515d", size = 2107629, upload-time = "2026-11-05T09:14:50.891Z" },
+ { url = "https://files.pythonhosted.org/packages/18/32/43d5db1824ec2314f737ff8d9962ff59954a74616d8656a8cc78790e45f1/pydantic_core-2.50.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f08ee8248aad2d054e5bc4c73554d4505464698f6d754f16198dbc5764e9821d", size = 2244906, upload-time = "2026-11-05T09:14:53.056Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c0/38c505004969c4641c3a077a8e72e354f02d5c06d8f51d7a103699704772/pydantic_core-2.50.4-cp315-cp315t-win_arm64.whl", hash = "sha256:da779ae50b46f3f7d1e1b5f362fdc0ad376a49fd55f6f3122dc2b6149936cdf7", size = 2179018, upload-time = "2026-11-05T09:14:54.448Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/18/2f6a25e9104d5af713d16671711927601da1f0db74e785be67bd590385c9/pydantic_core-2.50.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:c04e9d920e2674f54651fd260a15c5d2843526c73d5242ee4311325ddc9573b3", size = 2212591, upload-time = "2026-11-05T09:14:56.262Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/1b/ba8b721af10a7659e7fbbef320ff8eb55462557a72607858af5a9bd22cb1/pydantic_core-2.50.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:5bc1811ef2b4d4ba63e8e452f6e332b6374bd1d2cbbfdf66826c8cb1f71dd10d", size = 2048167, upload-time = "2026-11-05T09:14:57.681Z" },
+ { url = "https://files.pythonhosted.org/packages/48/42/3f8638e7d90bdd9a7c7f3e5084723af59e7f43f15a292e242d35624073ce/pydantic_core-2.50.4-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b059a7968b1911be5a2d9a8234827ab0bef9d1e465d4b42da525894143550e8", size = 2119548, upload-time = "2026-11-05T09:14:59.317Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/8d/4bfbacaf191f7939893d165e18eb301731fa3366c59a6e57336c4f168fbd/pydantic_core-2.50.4-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bee28aab5a580eec673f7b68dfa8dece3dd37bd207ef8d22fafe23a8e9837296", size = 2279952, upload-time = "2026-11-05T09:15:00.907Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/3a/d405a50e02ea81cd95e93fc97f43d351eeaa1707c28aa6fb58a9f1c1efa9/pydantic_core-2.50.4-graalpy313-graalpy253_313_native-macosx_11_0_arm64.whl", hash = "sha256:71e03d2ea1a221d020cfc5b6cc8bbbedef23a06704c70fcf4d0afb42965b22d1", size = 2049324, upload-time = "2026-11-05T09:15:02.379Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/3b/03e7992115e17cae4131ecd71aadb8fd5e4a04cc2aead72c3c27e9b453d1/pydantic_core-2.50.4-graalpy313-graalpy253_313_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09450778e3ab58d3f565899fcfbf6c82926e31af29e28e799d1d50941316e0cb", size = 2119802, upload-time = "2026-11-05T09:15:03.901Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/d5/b76fc647e473377ca6e693c21549ee3a4a7677e4962c28ae4659337c5114/pydantic_core-2.50.4-graalpy313-graalpy253_313_native-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4eb15be588407148ef5937dc8d9256a71f6b115e6cddadb7aa20b6ee384680dc", size = 2280639, upload-time = "2026-11-05T09:15:05.374Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/18/3ca5b3b8ce7a540305e6856d013e9c6b9c0fef93258fef6038db0beb233a/pydantic_core-2.50.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:39f489ce31272073e6e146f6781690f9c1ee8c126836e6403361ef91154d89fe", size = 2166960, upload-time = "2026-11-05T09:15:06.902Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/60/65d725001bffcde0170f6d37ae8bccce715e5311122eb6127f698115957e/pydantic_core-2.50.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b26e5f1793b96cdc60242bf38da86ff24a27a708aa396214cac9b534bf571c7", size = 2085636, upload-time = "2026-11-05T09:15:08.395Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/12/934cf1d44a3fee3c47231d4690dddee19264150c3aa59a3ddf66fa43454d/pydantic_core-2.50.4-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:7210398a1607ad607dc73196e575b3148b00a333cbb602ddbe1b60ba54554f43", size = 2284621, upload-time = "2026-11-05T09:15:09.82Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/91/52248eee98d76f8d9239166d24928f805292df16729109f4f189cdb59be4/pydantic_core-2.50.4-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f42584e5785385f681163a42506cab0d51295e17599a648396c52a7046c1372", size = 2277673, upload-time = "2026-11-05T09:15:11.236Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/89/900a7970ef19843aa79522a95b461e9c365b7fedd7ee80ef28a84dae9a41/pydantic_core-2.50.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2039e323acee9abfec1fb5f661776e8965fe5928f3b7f8431900a774cba5c317", size = 2297354, upload-time = "2026-11-05T09:15:12.798Z" },
+ { url = "https://files.pythonhosted.org/packages/de/ec/053d829437f70070cdd7116a0257cc0da651103328ff1e9e82954c45efd6/pydantic_core-2.50.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:71ade6d2e445839350d589ff99bbddfde16f3f32db28da5c894a09f99c8b2a03", size = 2469913, upload-time = "2026-11-05T09:15:14.319Z" },
+ { url = "https://files.pythonhosted.org/packages/80/af/207c821cb0813e16d630660e0073491e3ab07370efaa6fa0b8b10fffb7b6/pydantic_core-2.50.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d7d406709ae924cf0b08b8282ca2f0e1c90526911d93bc040557cbc83b74c55f", size = 2503746, upload-time = "2026-11-05T09:15:15.736Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/ca/8a053f24e22d8150349cabaecb2f35c89dcf07fbaadb5e48295184dd0e5b/pydantic_core-2.50.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7472c39480ccc11dda69a196d1b7c7a6b48eb6594b77f05d945599d3e47f731c", size = 2333540, upload-time = "2026-11-05T09:15:17.883Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/9d/41b374c62cdb87f84081e2ef2963a115eacc6284a26dbc96bb83568e83ec/pydantic_core-2.50.4-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:b67d38e8bb0ade7e6e84575e4bb153fab35b122458077273aeb08fdf12be9d2c", size = 2166962, upload-time = "2026-11-05T09:15:20.276Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/4f/51ef440ee346d4388a2c4a3ac4b271fcc7b1d3ff68be8da2ed2be4d488b0/pydantic_core-2.50.4-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4eda7e3fdef382b7d4492745988a5baaf6953923a337f8b9dde5f76fa8afde20", size = 2085634, upload-time = "2026-11-05T09:15:21.953Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/5f/c9ed62b96beb9050db221d29e81856b6e28f277db601f8b0c3a08f8cf913/pydantic_core-2.50.4-pp311-pypy311_pp80-manylinux_2_28_x86_64.whl", hash = "sha256:474f7e598c7902165662a5d5160d3fce31037890a2975a9871ebf7c05862300b", size = 2277804, upload-time = "2026-11-05T09:15:23.836Z" },
+ { url = "https://files.pythonhosted.org/packages/63/bd/b912a5e251ef10ed7be5e543a2bfbeebb0c79bf6302bee786f8dc3bf4341/pydantic_core-2.50.4-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:3e0b395648c6cb871bceb2906907f255dc876ec425f2d5b024e129985cea8519", size = 2333539, upload-time = "2026-11-05T09:15:25.354Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/79/42/bb4641652e58dedeac2ec51abc1f8cae4fee4ec63899a0999016229a0f2a/pytest-9.2.0.tar.gz", hash = "sha256:97c4f3b1f3c7414aa36465e09b8220f1d03627993bd51b1282e193753c80557b", size = 1797555, upload-time = "2026-10-27T21:30:20.728Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/39/1f5d8c8877a0fa327db959c19341d4be61660de1ab6b7391f9eb805bcd67/pytest-9.2.0-py3-none-any.whl", hash = "sha256:07acdcf88ec174f907dc01af70bf52d49b014bbab03faeeade31c96b18c2e588", size = 411776, upload-time = "2026-10-27T21:30:19.131Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.4.1.post1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/7f/c60a4338420b33453d4f814ac0c930abd399f162b9bdd21af4b9e363037f/pytest_asyncio-1.4.1.post1.tar.gz", hash = "sha256:f582d9ea4a1c082195abe8bd29a1fe106aeb1cd435e3dad2d3bc2749935c36a2", size = 145360, upload-time = "2026-11-01T18:26:03.732Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e3/de/adbc7578959cfab07e5e7c05cf6d54224a51e7667ea2c07458b6bc571e75/pytest_asyncio-1.4.1.post1-py3-none-any.whl", hash = "sha256:0755e488cc0a2b9bd2eab7292cc6390d4cbb8390f1020745647bae5a708e0165", size = 17140, upload-time = "2026-11-01T18:26:02.168Z" },
+]
+
+[[package]]
+name = "pytest-cov"
+version = "7.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "coverage" },
+ { name = "pluggy" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/39/b3927f3616f6223832f9b760e4208e8a7c7e1dea2b4d9ad7d32a5d214c3a/python_dateutil-4.0.0.tar.gz", hash = "sha256:d7295ff14fdc0f92f662100bfb6c551d160ca090f2939f1ec5b9794265c03f06", size = 250419, upload-time = "2026-11-01T07:02:42.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2f/a9/52b89563d154433d79b51ed1eb4842bf7334cb2696891a7fe135618853d7/python_dateutil-4.0.0-py3-none-any.whl", hash = "sha256:a5e246f79eb80475e691bf93862cead98decd8a498cf53ba0516fcdc0f50dc1c", size = 92853, upload-time = "2026-11-01T07:02:40.797Z" },
+]
+
+[[package]]
+name = "respx"
+version = "0.23.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "httpx" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/43/98/4e55c9c486404ec12373708d015ebce157966965a5ebe7f28ff2c784d41b/respx-0.23.1.tar.gz", hash = "sha256:242dcc6ce6b5b9bf621f5870c82a63997e8e82bc7c947f9ffe272b8f3dd5a780", size = 29243, upload-time = "2026-04-08T14:37:16.008Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1d/4a/221da6ca167db45693d8d26c7dc79ccfc978a440251bf6721c9aaf251ac0/respx-0.23.1-py2.py3-none-any.whl", hash = "sha256:b18004b029935384bccfa6d7d9d74b4ec9af73a081cc28600fffc0447f4b8c1a", size = 25557, upload-time = "2026-04-08T14:37:14.613Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.17.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" },
+ { url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" },
+ { url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" },
+ { url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" },
+ { url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" },
+ { url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" },
+ { url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" },
+ { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" },
+]
+
+[[package]]
+name = "types-python-dateutil"
+version = "2.9.0.20261104"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/da/aaf5b20ea6c9e714fae9fdc611b68bde4edcba0aa4b1b853fc157d3d7b37/types_python_dateutil-2.9.0.20261104.tar.gz", hash = "sha256:5d0495702a5f7343741f5d842c531bcd1c941da8b4a85307d52ef43ba80252db", size = 17861, upload-time = "2026-11-04T08:14:27.977Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/4d/f9beea6da46a838025209b06d91374c8af9fbb82c599167ae99ee59cfb7a/types_python_dateutil-2.9.0.20261104-py3-none-any.whl", hash = "sha256:f6ee76c91c547716b4795373a53d4c07e8e06cf98384ecb89fcf134d212de2ad", size = 18509, upload-time = "2026-11-04T08:14:26.989Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ff/50/0195198eb1d75bab716bb0a813abea4cc394bfa17a92a189f021a34859af/typing_inspection-0.4.5.tar.gz", hash = "sha256:7f16c411e7930dc79596d42ab44672a3fe5f60eb34b781a1647e7872363120c7", size = 81194, upload-time = "2026-11-04T14:13:23.774Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/de/0c/ea2544850a7c135e15033a5c7a8e7211136184b7ae63e2239d557c2b3019/typing_inspection-0.4.5-py3-none-any.whl", hash = "sha256:bc210535442b39e1bf2805e0c7a17dfd6a4ace760c84c7f7d16245d23a6b1c39", size = 14874, upload-time = "2026-11-04T14:13:22.616Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2026.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7a/29/6a1391d31cd00ff9e927215a224a880e1e84e826b64ba80a499e51c074d8/tzdata-2026.6.tar.gz", hash = "sha256:85d2cd9c09f81f0bb29db27759fc83af4f1e620efb814f8c5bf8b4cc4feece6d", size = 201017, upload-time = "2026-10-28T09:57:09.137Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/66/9a06c2227fedfd4919f0eaaa8b94377d9730882a7e4f95823c9a331ab5e1/tzdata-2026.6-py2.py3-none-any.whl", hash = "sha256:0a10eec6e13ef16b627fabed9a5f2ba61a62b1c67f327305f4e7e412a70b4aec", size = 348210, upload-time = "2026-10-28T09:57:07.276Z" },
+]
+
+[[package]]
+name = "wcmatch"
+version = "12.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "bracex" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/60/81/eb96ce41e8def4ff3b02cec7d1d92e3682597de5064d8687ac88d1734f18/wcmatch-12.0.tar.gz", hash = "sha256:80ebfdf510d5fef580c4fc662b9eba76b331cff20d7913fc6e722edcb199cbe6", size = 161277, upload-time = "2026-11-03T14:23:48.017Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/8d/fb9b889e282a73e4423bd10f1ac358dd68b3e78fd099fab17f270a482157/wcmatch-12.0-py3-none-any.whl", hash = "sha256:46f7965313cfe65d8be049ab91095a5908be96ebb43e5d16da608b158825113c", size = 50558, upload-time = "2026-11-03T14:23:46.592Z" },
+]
From c8024388bd88cf635142d277049dda9df93cff2d Mon Sep 17 00:00:00 2001
From: Tariod
Date: Fri, 6 Nov 2026 16:47:15 +0100
Subject: [PATCH 2/2] feat: add webhook signature checker
---
.openapi-generator-ignore | 1 +
README.md | 27 ++++++
examples/README.md | 13 +++
examples/webhooks.py | 49 ++++++++++
src/perigon/__init__.py | 6 ++
src/perigon/webhooks.py | 122 ++++++++++++++++++++++++
templates/__init__package.mustache | 6 ++
tests/test_webhooks.py | 148 +++++++++++++++++++++++++++++
8 files changed, 372 insertions(+)
create mode 100644 examples/webhooks.py
create mode 100644 src/perigon/webhooks.py
create mode 100644 tests/test_webhooks.py
diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore
index a9263e8..f25f393 100644
--- a/.openapi-generator-ignore
+++ b/.openapi-generator-ignore
@@ -24,3 +24,4 @@
perigon_README.md
src/perigon/rest.py
+src/perigon/webhooks.py
diff --git a/README.md b/README.md
index c3536a4..57df247 100644
--- a/README.md
+++ b/README.md
@@ -255,6 +255,33 @@ results = api.vector_search_wikipedia(
---
+## Webhooks
+
+Verify a delivery before reading the body. Pass the raw request bytes and the
+`perigon-signature` header. The default tolerance is 5 minutes.
+
+```python
+import os
+
+from perigon import WEBHOOK_HEADER, SignatureVerificationError, Webhooks
+
+webhooks = Webhooks()
+secret = os.environ["PERIGON_WEBHOOK_SECRET"]
+
+
+def handle(raw_body: bytes, signature: str | None):
+ try:
+ event = webhooks.construct_event(raw_body, signature, secret)
+ except SignatureVerificationError:
+ return None
+ return event
+```
+
+`signature` is the `perigon-signature` header (`WEBHOOK_HEADER`). A full
+listener is in `examples/webhooks.py`.
+
+---
+
## π Async Support
`AsyncV1Api` has the same method names. Each one is a coroutine:
diff --git a/examples/README.md b/examples/README.md
index 74d8c39..342af19 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -63,6 +63,19 @@ python examples/advanced.py
- Async/await patterns for concurrent operations
- Error handling for complex workflows
+### Webhooks (`webhooks.py`)
+
+Verify a raw webhook body against the `perigon-signature` header.
+
+**Run it:**
+```bash
+export PERIGON_WEBHOOK_SECRET="your-webhook-secret"
+python examples/webhooks.py
+```
+
+The server listens on `http://127.0.0.1:3000/webhook` and returns 400 when the
+signature does not verify.
+
## Example Output
### Basic Example
diff --git a/examples/webhooks.py b/examples/webhooks.py
new file mode 100644
index 0000000..4633e08
--- /dev/null
+++ b/examples/webhooks.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+"""
+Perigon SDK webhook signature example.
+
+Verifies the raw request body against the perigon-signature header.
+
+Before running: set PERIGON_WEBHOOK_SECRET
+Run: python examples/webhooks.py
+"""
+
+import os
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+
+from perigon import WEBHOOK_HEADER, SignatureVerificationError, Webhooks
+
+secret = os.environ.get("PERIGON_WEBHOOK_SECRET", "")
+webhooks = Webhooks()
+
+
+class WebhookHandler(BaseHTTPRequestHandler):
+ def do_POST(self) -> None:
+ if self.path != "/webhook":
+ self.send_error(404)
+ return
+
+ length = int(self.headers.get("Content-Length", "0"))
+ body = self.rfile.read(length)
+ signature = self.headers.get(WEBHOOK_HEADER)
+ try:
+ webhooks.construct_event(body, signature, secret)
+ except SignatureVerificationError as error:
+ print(error)
+ self.send_response(400)
+ self.end_headers()
+ return
+
+ self.send_response(200)
+ self.end_headers()
+
+
+def main() -> None:
+ port = 3000
+ server = ThreadingHTTPServer(("127.0.0.1", port), WebhookHandler)
+ print(f"Example app listening on port {port}")
+ server.serve_forever()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/perigon/__init__.py b/src/perigon/__init__.py
index 426527e..f58a2bf 100644
--- a/src/perigon/__init__.py
+++ b/src/perigon/__init__.py
@@ -31,6 +31,12 @@
from perigon.exceptions import ApiAttributeError
from perigon.exceptions import ApiException
+# import webhook signature verification
+from perigon.webhooks import WEBHOOK_HEADER
+from perigon.webhooks import SignatureVerificationError
+from perigon.webhooks import WebhookEvent
+from perigon.webhooks import Webhooks
+
# import models into sdk package
from perigon.models.all_endpoint_sort_by import AllEndpointSortBy
from perigon.models.api_key_status import ApiKeyStatus
diff --git a/src/perigon/webhooks.py b/src/perigon/webhooks.py
new file mode 100644
index 0000000..87fff0c
--- /dev/null
+++ b/src/perigon/webhooks.py
@@ -0,0 +1,122 @@
+"""Verify Perigon webhook deliveries.
+
+The ``perigon-signature`` header looks like ``t=,v1=``.
+The signed string is the raw body, ``f"{timestamp}.{payload}"``, using
+HMAC-SHA256. The default tolerance is five minutes.
+"""
+
+import hashlib
+import hmac
+import json
+import time
+from datetime import datetime
+from typing import Any
+
+from pydantic import BaseModel, ConfigDict
+
+WEBHOOK_HEADER = "perigon-signature"
+DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
+
+_SIGNATURE_MISMATCH = (
+ "Signature hash does not match the expected signature hash for payload."
+)
+_OUTSIDE_TOLERANCE = "Timestamp outside the tolerance zone."
+
+
+class SignatureVerificationError(Exception):
+ """A webhook header or payload failed signature verification."""
+
+
+class WebhookEvent(BaseModel):
+ """A verified Perigon webhook delivery."""
+
+ model_config = ConfigDict(extra="ignore")
+
+ signal_id: int
+ signal_notification_id: int
+ contact_point_notification_id: int
+ sent_at: datetime
+ metadata: dict[str, Any]
+
+
+class Webhooks:
+ """Check a raw webhook body against the ``perigon-signature`` header."""
+
+ def construct_event(
+ self,
+ payload: str | bytes,
+ header: str | bytes | list[str] | None,
+ secret: str,
+ tolerance: int = DEFAULT_TOLERANCE_MS,
+ ) -> WebhookEvent:
+ """Verify ``header`` and parse ``payload`` into a :class:`WebhookEvent`.
+
+ ``tolerance`` is how old the signature timestamp may be, in
+ milliseconds. A timestamp newer than the local clock is accepted.
+ """
+ timestamp, signature = _parse_header(header)
+ if timestamp < _now_ms() - tolerance:
+ raise SignatureVerificationError(_OUTSIDE_TOLERANCE)
+
+ body = _payload_text(payload)
+ digest = hmac.digest(
+ secret.encode(),
+ f"{timestamp}.{body}".encode(),
+ hashlib.sha256,
+ ).hex()
+ if not hmac.compare_digest(digest, signature):
+ raise SignatureVerificationError(_SIGNATURE_MISMATCH)
+
+ return WebhookEvent.model_validate(json.loads(body))
+
+
+def _parse_header(
+ header: str | bytes | list[str] | None,
+) -> tuple[int, str]:
+ if isinstance(header, (list, tuple)):
+ raise SignatureVerificationError(
+ "Unexpected: An array was passed as a header, which should "
+ f"not be possible for the {WEBHOOK_HEADER} header."
+ )
+ if isinstance(header, (bytes, bytearray)):
+ header = header.decode()
+ if not isinstance(header, str) or not header:
+ raise SignatureVerificationError(
+ f"No {WEBHOOK_HEADER} header value was provided."
+ )
+
+ try:
+ timestamp_part, signature_part, *_ = header.split(",")
+ except ValueError:
+ raise SignatureVerificationError("Signature or timestamp missing") from None
+
+ return (
+ _int_field(timestamp_part, "t", "Unable to extract timestamp from header"),
+ _field(signature_part, "v1", "No signature found with expected schema."),
+ )
+
+
+def _payload_text(payload: str | bytes) -> str:
+ if isinstance(payload, (bytes, bytearray)):
+ payload = payload.decode()
+ if not isinstance(payload, str) or not payload:
+ raise SignatureVerificationError("No webhook payload was provided.")
+ return payload
+
+
+def _field(part: str, key: str, message: str) -> str:
+ found, separator, value = part.partition("=")
+ if not separator or found != key or not value:
+ raise SignatureVerificationError(message)
+ return value
+
+
+def _int_field(part: str, key: str, message: str) -> int:
+ try:
+ return int(_field(part, key, message))
+ except ValueError:
+ raise SignatureVerificationError(message) from None
+
+
+def _now_ms() -> int:
+ return time.time_ns() // 1_000_000
diff --git a/templates/__init__package.mustache b/templates/__init__package.mustache
index 426616c..24532c6 100644
--- a/templates/__init__package.mustache
+++ b/templates/__init__package.mustache
@@ -24,6 +24,12 @@ from {{packageName}}.exceptions import ApiException
from {{packageName}}.signing import HttpSigningConfiguration
{{/hasHttpSignatureMethods}}
+# import webhook signature verification
+from {{packageName}}.webhooks import WEBHOOK_HEADER
+from {{packageName}}.webhooks import SignatureVerificationError
+from {{packageName}}.webhooks import WebhookEvent
+from {{packageName}}.webhooks import Webhooks
+
# import models into sdk package
{{#models}}
{{#model}}
diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py
new file mode 100644
index 0000000..32a1038
--- /dev/null
+++ b/tests/test_webhooks.py
@@ -0,0 +1,148 @@
+import hashlib
+import hmac
+import json
+from datetime import datetime, timezone
+
+import pytest
+from pydantic import ValidationError
+
+from perigon import WEBHOOK_HEADER, SignatureVerificationError, WebhookEvent, Webhooks
+from perigon.webhooks import DEFAULT_TOLERANCE_MS
+
+SECRET = "whsec_test"
+NOW_MS = 1_700_000_000_000
+PAYLOAD = json.dumps(
+ {
+ "signal_id": 11,
+ "signal_notification_id": 22,
+ "contact_point_notification_id": 33,
+ "sent_at": "2026-11-06T12:00:00Z",
+ "metadata": {"source": "unit"},
+ }
+)
+
+
+def _sign(payload: str, secret: str, timestamp: int) -> str:
+ digest = hmac.new(
+ secret.encode("utf-8"),
+ f"{timestamp}.{payload}".encode("utf-8"),
+ hashlib.sha256,
+ ).hexdigest()
+ return f"t={timestamp},v1={digest}"
+
+
+@pytest.fixture
+def webhooks(monkeypatch: pytest.MonkeyPatch) -> Webhooks:
+ monkeypatch.setattr("perigon.webhooks._now_ms", lambda: NOW_MS)
+ return Webhooks()
+
+
+def test_construct_event_accepts_string_payload(webhooks: Webhooks) -> None:
+ header = _sign(PAYLOAD, SECRET, NOW_MS)
+ event = webhooks.construct_event(PAYLOAD, header, SECRET)
+
+ assert isinstance(event, WebhookEvent)
+ assert event.signal_id == 11
+ assert event.signal_notification_id == 22
+ assert event.contact_point_notification_id == 33
+ assert event.sent_at == datetime(2026, 11, 6, 12, 0, tzinfo=timezone.utc)
+ assert event.metadata == {"source": "unit"}
+
+
+def test_construct_event_accepts_bytes_payload_and_header(webhooks: Webhooks) -> None:
+ header = _sign(PAYLOAD, SECRET, NOW_MS)
+ event = webhooks.construct_event(PAYLOAD.encode(), header.encode(), SECRET)
+
+ assert event.signal_id == 11
+
+
+def test_tampered_body_is_rejected(webhooks: Webhooks) -> None:
+ header = _sign(PAYLOAD, SECRET, NOW_MS)
+ tampered = PAYLOAD.replace("unit", "other")
+
+ with pytest.raises(SignatureVerificationError, match="does not match"):
+ webhooks.construct_event(tampered, header, SECRET)
+
+
+def test_wrong_secret_is_rejected(webhooks: Webhooks) -> None:
+ header = _sign(PAYLOAD, SECRET, NOW_MS)
+
+ with pytest.raises(SignatureVerificationError, match="does not match"):
+ webhooks.construct_event(PAYLOAD, header, "other-secret")
+
+
+def test_stale_timestamp_is_rejected(webhooks: Webhooks) -> None:
+ timestamp = NOW_MS - DEFAULT_TOLERANCE_MS - 1
+ header = _sign(PAYLOAD, SECRET, timestamp)
+
+ with pytest.raises(SignatureVerificationError, match="tolerance zone"):
+ webhooks.construct_event(PAYLOAD, header, SECRET)
+
+
+def test_timestamp_at_tolerance_boundary_is_accepted(webhooks: Webhooks) -> None:
+ timestamp = NOW_MS - DEFAULT_TOLERANCE_MS
+ header = _sign(PAYLOAD, SECRET, timestamp)
+
+ event = webhooks.construct_event(PAYLOAD, header, SECRET)
+
+ assert event.signal_id == 11
+
+
+def test_future_timestamp_is_accepted(webhooks: Webhooks) -> None:
+ header = _sign(PAYLOAD, SECRET, NOW_MS + 60_000)
+
+ event = webhooks.construct_event(PAYLOAD, header, SECRET)
+
+ assert event.signal_id == 11
+
+
+@pytest.mark.parametrize("header", [None, "", b""])
+def test_missing_header_is_rejected(
+ webhooks: Webhooks, header: str | bytes | None
+) -> None:
+ with pytest.raises(SignatureVerificationError, match=WEBHOOK_HEADER):
+ webhooks.construct_event(PAYLOAD, header, SECRET)
+
+
+@pytest.mark.parametrize(
+ "header",
+ [
+ "not-a-signature",
+ "t=1",
+ "t=abc,v1=abc",
+ "t=123abc,v1=abc",
+ "t=,v1=abc",
+ "v1=abc,t=1",
+ ],
+)
+def test_malformed_header_is_rejected(webhooks: Webhooks, header: str) -> None:
+ with pytest.raises(SignatureVerificationError):
+ webhooks.construct_event(PAYLOAD, header, SECRET)
+
+
+def test_header_list_is_rejected(webhooks: Webhooks) -> None:
+ with pytest.raises(SignatureVerificationError, match="array"):
+ webhooks.construct_event(PAYLOAD, ["t=1,v1=abc"], SECRET)
+
+
+def test_empty_payload_is_rejected(webhooks: Webhooks) -> None:
+ header = _sign("{}", SECRET, NOW_MS)
+
+ with pytest.raises(SignatureVerificationError, match="No webhook payload"):
+ webhooks.construct_event("", header, SECRET)
+
+
+def test_invalid_json_propagates(webhooks: Webhooks) -> None:
+ payload = "not-json"
+ header = _sign(payload, SECRET, NOW_MS)
+
+ with pytest.raises(json.JSONDecodeError):
+ webhooks.construct_event(payload, header, SECRET)
+
+
+def test_schema_failure_propagates(webhooks: Webhooks) -> None:
+ payload = json.dumps({"signal_id": 1})
+ header = _sign(payload, SECRET, NOW_MS)
+
+ with pytest.raises(ValidationError):
+ webhooks.construct_event(payload, header, SECRET)