diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index c39d485372..1886393d3c 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -7212,18 +7212,23 @@ export interface RichTextArticleRequest { title: string } /** - * * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @export * @enum {string} */ export const SearchModeEnumDescriptions = { + phrase: "phrase", best_fields: "best_fields", most_fields: "most_fields", - phrase: "phrase", + hybrid: "hybrid", } as const export const SearchModeEnum = { + /** + * phrase + */ + Phrase: "phrase", /** * best_fields */ @@ -7233,9 +7238,9 @@ export const SearchModeEnum = { */ MostFields: "most_fields", /** - * phrase + * hybrid */ - Phrase: "phrase", + Hybrid: "hybrid", } as const export type SearchModeEnum = @@ -17434,7 +17439,7 @@ export const LearningResourcesSearchApiAxiosParamCreator = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesSearchRetrieveSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesSearchRetrieveSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesSearchRetrieveSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] The topic name. To see a list of options go to api/v1/topics/ @@ -17650,7 +17655,7 @@ export const LearningResourcesSearchApiFp = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesSearchRetrieveSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesSearchRetrieveSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesSearchRetrieveSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] The topic name. To see a list of options go to api/v1/topics/ @@ -17959,8 +17964,8 @@ export interface LearningResourcesSearchApiLearningResourcesSearchRetrieveReques readonly resource_type?: Array /** - * The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase - * @type {'best_fields' | 'most_fields' | 'phrase'} + * The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid + * @type {'phrase' | 'best_fields' | 'most_fields' | 'hybrid'} * @memberof LearningResourcesSearchApiLearningResourcesSearchRetrieve */ readonly search_mode?: LearningResourcesSearchRetrieveSearchModeEnum @@ -18218,9 +18223,10 @@ export type LearningResourcesSearchRetrieveResourceTypeEnum = * @export */ export const LearningResourcesSearchRetrieveSearchModeEnum = { + Phrase: "phrase", BestFields: "best_fields", MostFields: "most_fields", - Phrase: "phrase", + Hybrid: "hybrid", } as const export type LearningResourcesSearchRetrieveSearchModeEnum = (typeof LearningResourcesSearchRetrieveSearchModeEnum)[keyof typeof LearningResourcesSearchRetrieveSearchModeEnum] @@ -18280,7 +18286,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionCheckListSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionCheckListSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionCheckListSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {LearningResourcesUserSubscriptionCheckListSourceTypeEnum} [source_type] The subscription type * `search_subscription_type` - search_subscription_type * `channel_subscription_type` - channel_subscription_type @@ -18489,7 +18495,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionListSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionListSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionListSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] The topic name. To see a list of options go to api/v1/topics/ @@ -18692,7 +18698,7 @@ export const LearningResourcesUserSubscriptionApiAxiosParamCreator = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionSubscribeCreateSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {LearningResourcesUserSubscriptionSubscribeCreateSourceTypeEnum} [source_type] The subscription type * `search_subscription_type` - search_subscription_type * `channel_subscription_type` - channel_subscription_type @@ -18974,7 +18980,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionCheckListSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionCheckListSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionCheckListSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {LearningResourcesUserSubscriptionCheckListSourceTypeEnum} [source_type] The subscription type * `search_subscription_type` - search_subscription_type * `channel_subscription_type` - channel_subscription_type @@ -19089,7 +19095,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionListSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionListSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionListSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] The topic name. To see a list of options go to api/v1/topics/ @@ -19201,7 +19207,7 @@ export const LearningResourcesUserSubscriptionApiFp = function ( * @param {string} [q] The search text * @param {Array} [resource_category] The category of learning resource * `course` - Course * `program` - Program * `learning_material` - Learning Material * @param {Array} [resource_type] The type of learning resource * `course` - course * `program` - program * `learning_path` - learning path * `podcast` - podcast * `podcast_episode` - podcast episode * `video` - video * `video_playlist` - video playlist * `article` - article - * @param {LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum} [search_mode] The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase + * @param {LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum} [search_mode] The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * @param {number | null} [slop] Allowed distance for phrase search * @param {LearningResourcesUserSubscriptionSubscribeCreateSortbyEnum} [sortby] If the parameter starts with \'-\' the sort is in descending order * `featured` - Featured * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {LearningResourcesUserSubscriptionSubscribeCreateSourceTypeEnum} [source_type] The subscription type * `search_subscription_type` - search_subscription_type * `channel_subscription_type` - channel_subscription_type @@ -19653,8 +19659,8 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr readonly resource_type?: Array /** - * The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase - * @type {'best_fields' | 'most_fields' | 'phrase'} + * The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid + * @type {'phrase' | 'best_fields' | 'most_fields' | 'hybrid'} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionCheckList */ readonly search_mode?: LearningResourcesUserSubscriptionCheckListSearchModeEnum @@ -19856,8 +19862,8 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr readonly resource_type?: Array /** - * The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase - * @type {'best_fields' | 'most_fields' | 'phrase'} + * The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid + * @type {'phrase' | 'best_fields' | 'most_fields' | 'hybrid'} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionList */ readonly search_mode?: LearningResourcesUserSubscriptionListSearchModeEnum @@ -20052,8 +20058,8 @@ export interface LearningResourcesUserSubscriptionApiLearningResourcesUserSubscr readonly resource_type?: Array /** - * The open search search type for text queries * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `phrase` - phrase - * @type {'best_fields' | 'most_fields' | 'phrase'} + * The open search search type for text queries * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields * `hybrid` - hybrid + * @type {'phrase' | 'best_fields' | 'most_fields' | 'hybrid'} * @memberof LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreate */ readonly search_mode?: LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum @@ -20454,9 +20460,10 @@ export type LearningResourcesUserSubscriptionCheckListResourceTypeEnum = * @export */ export const LearningResourcesUserSubscriptionCheckListSearchModeEnum = { + Phrase: "phrase", BestFields: "best_fields", MostFields: "most_fields", - Phrase: "phrase", + Hybrid: "hybrid", } as const export type LearningResourcesUserSubscriptionCheckListSearchModeEnum = (typeof LearningResourcesUserSubscriptionCheckListSearchModeEnum)[keyof typeof LearningResourcesUserSubscriptionCheckListSearchModeEnum] @@ -20661,9 +20668,10 @@ export type LearningResourcesUserSubscriptionListResourceTypeEnum = * @export */ export const LearningResourcesUserSubscriptionListSearchModeEnum = { + Phrase: "phrase", BestFields: "best_fields", MostFields: "most_fields", - Phrase: "phrase", + Hybrid: "hybrid", } as const export type LearningResourcesUserSubscriptionListSearchModeEnum = (typeof LearningResourcesUserSubscriptionListSearchModeEnum)[keyof typeof LearningResourcesUserSubscriptionListSearchModeEnum] @@ -20863,9 +20871,10 @@ export type LearningResourcesUserSubscriptionSubscribeCreateResourceTypeEnum = * @export */ export const LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum = { + Phrase: "phrase", BestFields: "best_fields", MostFields: "most_fields", - Phrase: "phrase", + Hybrid: "hybrid", } as const export type LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum = (typeof LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum)[keyof typeof LearningResourcesUserSubscriptionSubscribeCreateSearchModeEnum] diff --git a/frontends/main/src/app/c/[channelType]/[name]/page.tsx b/frontends/main/src/app/c/[channelType]/[name]/page.tsx index bbe9af0cba..cba926a981 100644 --- a/frontends/main/src/app/c/[channelType]/[name]/page.tsx +++ b/frontends/main/src/app/c/[channelType]/[name]/page.tsx @@ -99,6 +99,7 @@ const Page: React.FC> = async ({ ) const searchRequest = getSearchParams({ + // @ts-expect-error -- this will error until mitodl/mit-learn-api-axios is updated requestParams: validateRequestParams(search), constantSearchParams, facetNames, diff --git a/frontends/main/src/app/search/page.tsx b/frontends/main/src/app/search/page.tsx index 22cfc70244..b6e429dbaa 100644 --- a/frontends/main/src/app/search/page.tsx +++ b/frontends/main/src/app/search/page.tsx @@ -28,6 +28,7 @@ const Page: React.FC> = async ({ searchParams }) => { } const params = getSearchParams({ + // @ts-expect-error -- this will error until mitodl/mit-learn-api-axios is updated requestParams: validateRequestParams(search), constantSearchParams: {}, facetNames, diff --git a/learning_resources_search/api.py b/learning_resources_search/api.py index 77394639f9..55929e0077 100644 --- a/learning_resources_search/api.py +++ b/learning_resources_search/api.py @@ -13,12 +13,15 @@ from learning_resources.models import LearningResource from learning_resources_search.connection import ( get_default_alias_name, + get_vector_model_id, ) from learning_resources_search.constants import ( + COMBINED_INDEX, CONTENT_FILE_TYPE, COURSE_QUERY_FIELDS, COURSE_TYPE, DEPARTMENT_QUERY_FIELDS, + HYBRID_SEARCH_MODE, LEARNING_RESOURCE, LEARNING_RESOURCE_QUERY_FIELDS, LEARNING_RESOURCE_SEARCH_SORTBY_OPTIONS, @@ -66,7 +69,7 @@ def gen_content_file_id(content_file_id): return f"cf_{content_file_id}" -def relevant_indexes(resource_types, aggregations, endpoint): +def relevant_indexes(resource_types, aggregations, endpoint, use_hybrid_search): """ Return list of relevent index type for the query @@ -74,6 +77,7 @@ def relevant_indexes(resource_types, aggregations, endpoint): resource_types (list): the resource type parameter for the search aggregations (list): the aggregations parameter for the search endpoint (string): the endpoint: learning_resource or content_file + use_hybrid_search (bool): whether to use hybrid search Returns: Array(string): array of index names @@ -81,6 +85,8 @@ def relevant_indexes(resource_types, aggregations, endpoint): """ if endpoint == CONTENT_FILE_TYPE: return [get_default_alias_name(COURSE_TYPE)] + elif use_hybrid_search: + return [get_default_alias_name(COMBINED_INDEX)] if aggregations and "resource_type" in aggregations: return map(get_default_alias_name, LEARNING_RESOURCE_TYPES) @@ -143,7 +149,11 @@ def generate_sort_clause(search_params): return sort -def wrap_text_clause(text_query, min_score=None): +def wrap_text_clause( + text_query, + use_hybrid_search, + min_score=None, +): """ Wrap the text subqueries in a bool query Shared by generate_content_file_text_clause and @@ -151,10 +161,12 @@ def wrap_text_clause(text_query, min_score=None): Args: text_query (dict): dictionary with the opensearch text clauses + min_score (float): minimum score for function score query + use_hybrid_search (bool): whether to use hybrid search Returns: dict: dictionary with the opensearch text clause """ - if min_score and text_query: + if not use_hybrid_search and min_score and text_query: text_bool_clause = [ {"function_score": {"query": {"bool": text_query}, "min_score": min_score}} ] @@ -207,7 +219,7 @@ def generate_content_file_text_clause(text): else: text_query = {} - return wrap_text_clause(text_query) + return wrap_text_clause(text_query, use_hybrid_search=False) def generate_learning_resources_text_clause( @@ -222,16 +234,23 @@ def generate_learning_resources_text_clause( dict: dictionary with the opensearch text clause """ + use_hybrid_search = search_mode == HYBRID_SEARCH_MODE + query_type = ( "query_string" if text.startswith('"') and text.endswith('"') else "multi_match" ) extra_params = {} - if query_type == "multi_match" and search_mode: - extra_params["type"] = search_mode + if use_hybrid_search: + text_search_mode = settings.DEFAULT_SEARCH_MODE + else: + text_search_mode = search_mode + + if query_type == "multi_match" and text_search_mode: + extra_params["type"] = text_search_mode - if search_mode == "phrase" and slop: + if text_search_mode == "phrase" and slop: extra_params["slop"] = slop if content_file_score_weight is not None: @@ -335,7 +354,7 @@ def generate_learning_resources_text_clause( else: text_query = {} - return wrap_text_clause(text_query, min_score) + return wrap_text_clause(text_query, use_hybrid_search, min_score) def generate_filter_clause( @@ -573,7 +592,9 @@ def percolate_matches_for_document(document_id): return percolated_queries -def add_text_query_to_search(search, text, search_params, query_type_query): +def add_text_query_to_search( + search, text, search_params, query_type_query, use_hybrid_search +): if search_params.get("endpoint") == CONTENT_FILE_TYPE: text_query = generate_content_file_text_clause(text) else: @@ -590,7 +611,7 @@ def add_text_query_to_search(search, text, search_params, query_type_query): search_params.get("max_incompleteness_penalty", 0) / 100 ) - if yearly_decay_percent or max_incompleteness_penalty: + if not use_hybrid_search and (yearly_decay_percent or max_incompleteness_penalty): script_query = { "function_score": { "query": {"bool": {"must": [text_query], "filter": query_type_query}} @@ -626,14 +647,56 @@ def add_text_query_to_search(search, text, search_params, query_type_query): "params": params, } - search = search.query(script_query) + text_query = script_query + else: + text_query = {"bool": {"must": [text_query], "filter": query_type_query}} + + if use_hybrid_search: + vector_model_id = get_vector_model_id() + if not vector_model_id: + log.error("Vector model not found. Cannot perform hybrid search.") + error_message = "Vector model not found." + raise ValueError(error_message) + + vector_query_description = { + "neural": { + "description_embedding": { + "query_text": text, + "model_id": vector_model_id, + "min_score": 0.015, + }, + } + } + + vector_query_title = { + "neural": { + "title_embedding": { + "query_text": text, + "model_id": vector_model_id, + "min_score": 0.015, + }, + } + } + + search = search.extra( + query={ + "hybrid": { + "pagination_depth": 10, + "queries": [ + text_query, + vector_query_description, + vector_query_title, + ], + } + } + ) else: - search = search.query("bool", must=[text_query], filter=query_type_query) + search = search.query(text_query) return search -def construct_search(search_params): +def construct_search(search_params): # noqa: C901 """ Construct a learning resources search based on the query @@ -652,16 +715,20 @@ def construct_search(search_params): ): search_params["resource_type"] = list(LEARNING_RESOURCE_TYPES) + use_hybrid_search = search_params.get("search_mode") == HYBRID_SEARCH_MODE + indexes = relevant_indexes( search_params.get("resource_type"), search_params.get("aggregations"), search_params.get("endpoint"), + use_hybrid_search, ) search = Search(index=",".join(indexes)) search = search.source(fields={"excludes": SOURCE_EXCLUDED_FIELDS}) - search = search.params(search_type="dfs_query_then_fetch") + if not use_hybrid_search: + search = search.params(search_type="dfs_query_then_fetch") if search_params.get("offset"): search = search.extra(from_=search_params.get("offset")) @@ -683,14 +750,9 @@ def construct_search(search_params): text = re.sub("[\u201c\u201d]", '"', search_params.get("q")) search = add_text_query_to_search( - search, - text, - search_params, - query_type_query, + search, text, search_params, query_type_query, use_hybrid_search ) - suggest = generate_suggest_clause(text) - search = search.extra(suggest=suggest) else: search = search.query(query_type_query) @@ -727,6 +789,7 @@ def execute_learn_search(search_params): search_params["yearly_decay_percent"] = ( settings.DEFAULT_SEARCH_STALENESS_PENALTY ) + if search_params.get("search_mode") is None: search_params["search_mode"] = settings.DEFAULT_SEARCH_MODE if search_params.get("slop") is None: @@ -738,6 +801,25 @@ def execute_learn_search(search_params): settings.DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY ) search = construct_search(search_params) + + if search_params.get("search_mode") == HYBRID_SEARCH_MODE: + search = search.extra( + search_pipeline={ + "description": "Post processor for hybrid search", + "phase_results_processors": [ + { + "normalization-processor": { + "normalization": {"technique": "min_max"}, + "combination": { + "technique": "arithmetic_mean", + "parameters": {"weights": [0.6, 0.2, 0.2]}, + }, + } + } + ], + } + ) + results = search.execute().to_dict() if results.get("_shards", {}).get("failures"): log.error( @@ -904,7 +986,9 @@ def get_similar_topics( list of str: list of topic values """ - indexes = relevant_indexes([COURSE_TYPE], [], endpoint=LEARNING_RESOURCE) + indexes = relevant_indexes( + [COURSE_TYPE], [], endpoint=LEARNING_RESOURCE, use_hybrid_search=False + ) search = Search(index=",".join(indexes)) search = search.filter("term", resource_type=COURSE_TYPE) search = search.query( @@ -1051,7 +1135,9 @@ def get_similar_resources_opensearch( list of str: list of learning resources """ - indexes = relevant_indexes(LEARNING_RESOURCE_TYPES, [], endpoint=LEARNING_RESOURCE) + indexes = relevant_indexes( + LEARNING_RESOURCE_TYPES, [], endpoint=LEARNING_RESOURCE, use_hybrid_search=False + ) search = Search(index=",".join(indexes)) if num_resources: # adding +1 to num_resources since we filter out existing resource.id diff --git a/learning_resources_search/api_test.py b/learning_resources_search/api_test.py index 1ca59b7721..4adb5feef5 100644 --- a/learning_resources_search/api_test.py +++ b/learning_resources_search/api_test.py @@ -64,7 +64,21 @@ def os_topic(topic_name) -> Mock: ], ) def test_relevant_indexes(endpoint, resourse_types, aggregations, result): - assert list(relevant_indexes(resourse_types, aggregations, endpoint)) == result + assert ( + list( + relevant_indexes( + resourse_types, aggregations, endpoint, use_hybrid_search=False + ) + ) + == result + ) + # always return the hybrid index when use_hybrid_search is True + if endpoint == LEARNING_RESOURCE: + assert list( + relevant_indexes( + resourse_types, aggregations, endpoint, use_hybrid_search=True + ) + ) == ["testindex_combined_hybrid_default"] @pytest.mark.parametrize( @@ -134,18 +148,23 @@ def test_generate_sort_clause(sort_param, departments, result): assert generate_sort_clause(params) == result -@pytest.mark.parametrize("search_mode", ["best_fields", "most_fields", "phrase", None]) +@pytest.mark.parametrize( + "search_mode", ["best_fields", "most_fields", "phrase", "hybrid", None] +) @pytest.mark.parametrize("slop", [None, 2]) @pytest.mark.parametrize("content_file_score_weight", [None, 0, 0.5, 1]) def test_generate_learning_resources_text_clause( - search_mode, slop, content_file_score_weight + settings, search_mode, slop, content_file_score_weight ): extra_params = {} + settings.DEFAULT_SEARCH_MODE = "phrase" - if search_mode: + if search_mode == "hybrid": + extra_params["type"] = "phrase" + elif search_mode: extra_params["type"] = search_mode - if search_mode == "phrase" and slop: + if extra_params.get("type") == "phrase" and slop: extra_params["slop"] = slop min_score = 0 @@ -607,6 +626,7 @@ def test_generate_learning_resources_text_clause( ], } } + assert ( generate_learning_resources_text_clause( "math", search_mode, slop, content_file_score_weight, min_score @@ -1864,45 +1884,6 @@ def test_execute_learn_search_for_learning_resource_query(opensearch): "sort": [{"readable_id": {"order": "desc"}}], "from": 1, "size": 1, - "suggest": { - "text": "math", - "title.trigram": { - "phrase": { - "field": "title.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "title.trigram"}, - "prune": True, - }, - } - }, - "description.trigram": { - "phrase": { - "field": "description.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "description.trigram"}, - "prune": True, - }, - } - }, - }, "aggs": { "offered_by": { "aggs": { @@ -1965,6 +1946,8 @@ def test_execute_learn_search_for_learning_resource_query(opensearch): "content", "summary", "flashcards", + "description_embedding", + "title_embedding", ] }, } @@ -2350,44 +2333,420 @@ def test_execute_learn_search_with_script_score( "sort": [{"readable_id": {"order": "desc"}}], "from": 1, "size": 1, - "suggest": { - "text": "math", - "title.trigram": { - "phrase": { - "field": "title.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} + "aggs": { + "offered_by": { + "aggs": { + "offered_by": { + "nested": {"path": "offered_by"}, + "aggs": { + "offered_by": { + "terms": {"field": "offered_by.code", "size": 10000}, + "aggs": {"root": {"reverse_nested": {}}}, } }, - "params": {"field_name": "title.trigram"}, - "prune": True, + } + }, + "filter": { + "bool": { + "must": [ + { + "bool": { + "should": [ + { + "term": { + "resource_type": { + "value": "course", + "case_insensitive": True, + } + } + } + ] + } + }, + { + "bool": { + "should": [ + { + "term": { + "free": { + "value": True, + "case_insensitive": True, + } + } + } + ] + } + }, + ] + } + }, + } + }, + "_source": { + "excludes": [ + "created_on", + "course.course_numbers.sort_coursenum", + "course.course_numbers.primary", + "resource_relations", + "is_learning_material", + "resource_age_date", + "featured_rank", + "is_incomplete_or_stale", + "content", + "summary", + "flashcards", + "description_embedding", + "title_embedding", + ] + }, + } + + assert execute_learn_search(search_params) == opensearch.conn.search.return_value + + opensearch.conn.search.assert_called_once_with( + body=query, + index=["testindex_course_default"], + search_type="dfs_query_then_fetch", + ) + + +def test_execute_learn_search_with_hybrid_search(mocker, settings, opensearch): + opensearch.conn.search.return_value = { + "hits": {"total": {"value": 10, "relation": "eq"}} + } + + settings.DEFAULT_SEARCH_MODE = "best_fields" + + mocker.patch( + "learning_resources_search.api.get_vector_model_id", + return_value="vector_model_id", + ) + + search_params = { + "aggregations": ["offered_by"], + "q": "math", + "resource_type": ["course"], + "free": [True], + "limit": 1, + "offset": 1, + "sortby": "-readable_id", + "endpoint": LEARNING_RESOURCE, + "search_mode": "hybrid", + } + + query = { + "post_filter": { + "bool": { + "must": [ + { + "bool": { + "should": [ + { + "term": { + "resource_type": { + "value": "course", + "case_insensitive": True, + } + } + } + ] + } }, - } - }, - "description.trigram": { - "phrase": { - "field": "description.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} + { + "bool": { + "should": [ + { + "term": { + "free": { + "value": True, + "case_insensitive": True, + } + } + } + ] + } + }, + ] + } + }, + "sort": [{"readable_id": {"order": "desc"}}], + "from": 1, + "size": 1, + "query": { + "hybrid": { + "pagination_depth": 10, + "queries": [ + { + "bool": { + "must": [ + { + "bool": { + "filter": { + "bool": { + "must": [ + { + "bool": { + "should": [ + { + "multi_match": { + "query": "math", + "fields": [ + "title.english^3", + "description.english^2", + "full_description.english", + "platform.name", + "readable_id", + "offered_by", + "course_feature", + "video.transcript.english", + ], + "type": "best_fields", + } + }, + { + "nested": { + "path": "topics", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "topics.name" + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "departments", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "departments.department_id", + "departments.name", + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "course.course_numbers", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "course.course_numbers.value^5" + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "runs", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "runs.year", + "runs.semester", + "runs.level", + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "runs", + "query": { + "nested": { + "path": "runs.instructors", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "runs.instructors.last_name^5", + "runs.instructors.full_name^5", + ], + "type": "best_fields", + } + }, + } + }, + } + }, + { + "has_child": { + "type": "content_file", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "content.english", + "title.english", + "content_title.english", + "description.english", + "content_feature_type", + ], + "type": "best_fields", + } + }, + "score_mode": "avg", + } + }, + ] + } + } + ] + } + }, + "should": [ + { + "multi_match": { + "query": "math", + "fields": [ + "title.english^3", + "description.english^2", + "full_description.english", + "platform.name", + "readable_id", + "offered_by", + "course_feature", + "video.transcript.english", + ], + "type": "best_fields", + } + }, + { + "nested": { + "path": "topics", + "query": { + "multi_match": { + "query": "math", + "fields": ["topics.name"], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "departments", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "departments.department_id", + "departments.name", + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "course.course_numbers", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "course.course_numbers.value^5" + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "runs", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "runs.year", + "runs.semester", + "runs.level", + ], + "type": "best_fields", + } + }, + } + }, + { + "nested": { + "path": "runs", + "query": { + "nested": { + "path": "runs.instructors", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "runs.instructors.last_name^5", + "runs.instructors.full_name^5", + ], + "type": "best_fields", + } + }, + } + }, + } + }, + { + "has_child": { + "type": "content_file", + "query": { + "multi_match": { + "query": "math", + "fields": [ + "content.english", + "title.english", + "content_title.english", + "description.english", + "content_feature_type", + ], + "type": "best_fields", + } + }, + "score_mode": "avg", + } + }, + ], + } + } + ], + "filter": {"exists": {"field": "resource_type"}}, + } + }, + { + "neural": { + "description_embedding": { + "query_text": "math", + "model_id": "vector_model_id", + "min_score": 0.015, } - }, - "params": {"field_name": "description.trigram"}, - "prune": True, + } }, - } - }, + { + "neural": { + "title_embedding": { + "query_text": "math", + "model_id": "vector_model_id", + "min_score": 0.015, + } + } + }, + ], + } }, "aggs": { "offered_by": { @@ -2438,6 +2797,20 @@ def test_execute_learn_search_with_script_score( }, } }, + "search_pipeline": { + "description": "Post processor for hybrid search", + "phase_results_processors": [ + { + "normalization-processor": { + "normalization": {"technique": "min_max"}, + "combination": { + "technique": "arithmetic_mean", + "parameters": {"weights": [0.6, 0.2, 0.2]}, + }, + } + } + ], + }, "_source": { "excludes": [ "created_on", @@ -2451,6 +2824,8 @@ def test_execute_learn_search_with_script_score( "content", "summary", "flashcards", + "description_embedding", + "title_embedding", ] }, } @@ -2459,8 +2834,7 @@ def test_execute_learn_search_with_script_score( opensearch.conn.search.assert_called_once_with( body=query, - index=["testindex_course_default"], - search_type="dfs_query_then_fetch", + index=["testindex_combined_hybrid_default"], ) @@ -2781,45 +3155,6 @@ def test_execute_learn_search_with_min_score(mocker, settings, opensearch): "sort": [{"readable_id": {"order": "desc"}}], "from": 1, "size": 1, - "suggest": { - "text": "math", - "title.trigram": { - "phrase": { - "field": "title.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "title.trigram"}, - "prune": True, - }, - } - }, - "description.trigram": { - "phrase": { - "field": "description.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "description.trigram"}, - "prune": True, - }, - } - }, - }, "aggs": { "offered_by": { "aggs": { @@ -2882,6 +3217,8 @@ def test_execute_learn_search_with_min_score(mocker, settings, opensearch): "content", "summary", "flashcards", + "description_embedding", + "title_embedding", ] }, } @@ -3011,45 +3348,6 @@ def test_execute_learn_search_for_content_file_query(opensearch): }, "from": 1, "size": 1, - "suggest": { - "text": "math", - "title.trigram": { - "phrase": { - "field": "title.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "title.trigram"}, - "prune": True, - }, - } - }, - "description.trigram": { - "phrase": { - "field": "description.trigram", - "size": 5, - "gram_size": 1, - "confidence": 0.0001, - "max_errors": 3, - "collate": { - "query": { - "source": { - "match_phrase": {"{{field_name}}": "{{suggestion}}"} - } - }, - "params": {"field_name": "description.trigram"}, - "prune": True, - }, - } - }, - }, "aggs": { "offered_by": { "aggs": { @@ -3098,6 +3396,8 @@ def test_execute_learn_search_for_content_file_query(opensearch): "content", "summary", "flashcards", + "description_embedding", + "title_embedding", ] }, } diff --git a/learning_resources_search/connection.py b/learning_resources_search/connection.py index 8fb5fd893d..a8c1b45f63 100644 --- a/learning_resources_search/connection.py +++ b/learning_resources_search/connection.py @@ -135,3 +135,33 @@ def refresh_index(index): """ conn = get_conn() conn.indices.refresh(index) + + +def get_vector_model_id(): + """ + Get the model ID for the currently loaded vector model + """ + conn = get_conn() + model_name = settings.OPENSEARCH_VECTOR_MODEL_NAME + body = {"query": {"term": {"name.keyword": model_name}}} + models = conn.transport.perform_request( + "GET", "/_plugins/_ml/models/_search", body=body + ) + + if len(models.get("hits", {}).get("hits", [])) > 0: + return models["hits"]["hits"][0]["_source"]["model_id"] + + return None + + +def get_vector_model_info(): + """ + Get information about the currently loaded vector model + """ + + conn = get_conn() + model_id = get_vector_model_id() + if not model_id: + return None + + return conn.transport.perform_request("GET", f"/_plugins/_ml/models/{model_id}") diff --git a/learning_resources_search/connection_test.py b/learning_resources_search/connection_test.py index 507a04768e..037fb870d0 100644 --- a/learning_resources_search/connection_test.py +++ b/learning_resources_search/connection_test.py @@ -42,6 +42,8 @@ def test_get_active_aliases(mocker, index_types, indexes_exist, object_types): assert active_aliases == [ "testindex_percolator_default", "testindex_percolator_reindexing", + "testindex_combined_hybrid_default", + "testindex_combined_hybrid_reindexing", "testindex_course_default", "testindex_course_reindexing", "testindex_program_default", @@ -60,6 +62,7 @@ def test_get_active_aliases(mocker, index_types, indexes_exist, object_types): elif index_types == IndexestoUpdate.current_index.value: assert active_aliases == [ "testindex_percolator_default", + "testindex_combined_hybrid_default", "testindex_course_default", "testindex_program_default", "testindex_podcast_default", @@ -71,6 +74,7 @@ def test_get_active_aliases(mocker, index_types, indexes_exist, object_types): elif index_types == IndexestoUpdate.reindexing_index.value: assert active_aliases == [ "testindex_percolator_reindexing", + "testindex_combined_hybrid_reindexing", "testindex_course_reindexing", "testindex_program_reindexing", "testindex_podcast_reindexing", diff --git a/learning_resources_search/constants.py b/learning_resources_search/constants.py index e1e0bc0207..a1922ca39f 100644 --- a/learning_resources_search/constants.py +++ b/learning_resources_search/constants.py @@ -22,8 +22,10 @@ CURRENT_INDEX = "current_index" REINDEXING_INDEX = "reindexing_index" BOTH_INDEXES = "all_indexes" +COMBINED_INDEX = "combined_hybrid" LEARNING_RESOURCE = "learning_resource" +HYBRID_SEARCH_MODE = "hybrid" class IndexestoUpdate(Enum): @@ -46,7 +48,8 @@ class IndexestoUpdate(Enum): ARTICLE_TYPE, ) -BASE_INDEXES = (PERCOLATE_INDEX_TYPE,) + +BASE_INDEXES = (PERCOLATE_INDEX_TYPE, COMBINED_INDEX) ALL_INDEX_TYPES = BASE_INDEXES + LEARNING_RESOURCE_TYPES @@ -319,6 +322,27 @@ class FilterConfig: "max_weekly_hours": {"type": "integer"}, } +EMBEDDING_FIELDS = { + "title_embedding": { + "type": "knn_vector", + "method": { + "engine": "lucene", + "space_type": "l2", + "name": "hnsw", + "parameters": {}, + }, + }, + "description_embedding": { + "type": "knn_vector", + "method": { + "engine": "lucene", + "space_type": "l2", + "name": "hnsw", + "parameters": {}, + }, + }, +} + CONTENT_FILE_MAP = { "id": {"type": "long"}, @@ -446,6 +470,8 @@ class FilterConfig: "content", "summary", "flashcards", + "description_embedding", + "title_embedding", ] LEARNING_RESOURCE_SEARCH_SORTBY_OPTIONS = { diff --git a/learning_resources_search/indexing_api.py b/learning_resources_search/indexing_api.py index c5baf10019..2f4eb5aa85 100644 --- a/learning_resources_search/indexing_api.py +++ b/learning_resources_search/indexing_api.py @@ -8,6 +8,7 @@ from django.conf import settings from django.contrib.auth import get_user_model +from opensearch_py_ml.ml_commons import MLCommonClient from opensearchpy.exceptions import ConflictError, NotFoundError from opensearchpy.helpers import BulkIndexError, bulk @@ -17,13 +18,18 @@ get_conn, get_default_alias_name, get_reindexing_alias_name, + get_vector_model_id, + get_vector_model_info, make_backing_index_name, refresh_index, ) from learning_resources_search.constants import ( ALIAS_ALL_INDICES, ALL_INDEX_TYPES, + COMBINED_INDEX, COURSE_TYPE, + EMBEDDING_FIELDS, + LEARNING_RESOURCE_MAP, MAPPING, PERCOLATE_INDEX_TYPE, SYNONYMS, @@ -184,8 +190,24 @@ def clear_and_create_index(*, index_name=None, skip_mapping=False, object_type=N }, } } - if not skip_mapping: + + if object_type == COMBINED_INDEX: + model_info = get_vector_model_info() + if not model_info: + return + index_create_data["settings"]["index.knn"] = True + index_create_data["settings"]["default_pipeline"] = "vector_ingest_pipeline" + + embedding_dimension = model_info["model_config"]["embedding_dimension"] + embedding_mapping = EMBEDDING_FIELDS + for field in embedding_mapping.values(): + field["dimension"] = embedding_dimension + index_create_data["mappings"] = { + "properties": (LEARNING_RESOURCE_MAP | embedding_mapping) + } + elif not skip_mapping: index_create_data["mappings"] = {"properties": MAPPING[object_type]} + # from https://www.elastic.co/guide/en/elasticsearch/guide/current/asciifolding-token-filter.html conn.indices.create(index_name, body=index_create_data) @@ -306,35 +328,35 @@ def index_items(documents, object_type, index_types, **kwargs): raise ReindexError(msg) -def index_learning_resources(ids, resource_type, index_types): +def index_learning_resources(ids, base_index_name, index_types): """ Index a list of learning resources by id Args: ids(list of int): List of learning resource id's - resource_type: The resource type of the resources + base_index_name: The resource type of the resources index_types (string): one of the values IndexestoUpdate. Whether the default index, the reindexing index or both need to be updated """ - index_items(serialize_bulk_learning_resources(ids), resource_type, index_types) + index_items(serialize_bulk_learning_resources(ids), base_index_name, index_types) -def deindex_learning_resources(ids, resource_type): +def deindex_learning_resources(ids, base_index_name): """ Deindex a list of learning resources by id Args: ids(list of int): List of learning resource ids - resource_type: resource type + base_index_name: The name of the index to deindex from """ deindex_items( serialize_bulk_learning_resources_for_deletion(ids), - resource_type, + base_index_name, index_types=IndexestoUpdate.all_indexes.value, ) - if resource_type == COURSE_TYPE: + if base_index_name == COURSE_TYPE: for run_id in LearningResourceRun.objects.filter( learning_resource_id__in=ids ).values_list("id", flat=True): @@ -628,3 +650,85 @@ def get_existing_reindexing_indexes(obj_types): break return reindexing_indexes + + +def update_local_index_settings_for_hybrid_search(): + """ + Update local OpenSearch cluster settings for hybrid search. + On production only_run_on_ml_node should be set to false and + there should be dedicated ML nodes. + """ + settings_body = { + "persistent": { + "archived.plugins.index_state_management.metadata_migration.status": None, + "archived.plugins.index_state_management.template_migration.control": None, + } + } + conn = get_conn() + conn.cluster.put_settings(body=settings_body) + settings_body = { + "persistent": { + "plugins": { + "ml_commons": { + "only_run_on_ml_node": "false", + "native_memory_threshold": "99", + } + } + } + } + conn = get_conn() + conn.cluster.put_settings(body=settings_body) + + +def get_ml_client(): + """ + Get an MLCommonClient for the OpenSearch + """ + conn = get_conn() + return MLCommonClient(conn) + + +def register_model(): + """ + Register the vector OpenSearch model if it does not already exist . + """ + model_id = get_vector_model_id() + if model_id: + log.error("Model already registered.") + return + + ml_client = get_ml_client() + ml_client.register_pretrained_model( + model_name=settings.OPENSEARCH_VECTOR_MODEL_NAME, + model_version=settings.OPENSEARCH_VECTOR_MODEL_NAME_VERSION, + model_format=settings.OPENSEARCH_VECTOR_MODEL_FORMAT, + deploy_model=True, + ) + + +def create_ingest_pipeline(): + """ + Create an ingest pipeline for the combined_vector index for text embedding. + """ + conn = get_conn() + model_id = get_vector_model_id() + if not model_id: + log.error("Model not found. Cannot create ingest pipeline.") + return + + pipeline = { + "description": "An NLP ingest pipeline", + "processors": [ + { + "text_embedding": { + "model_id": model_id, + "field_map": { + "description": "description_embedding", + "title": "title_embedding", + }, + } + } + ], + } + + conn.ingest.put_pipeline("vector_ingest_pipeline", pipeline) diff --git a/learning_resources_search/management/commands/recreate_index.py b/learning_resources_search/management/commands/recreate_index.py index 6d47b10b63..6bf13d1c1e 100644 --- a/learning_resources_search/management/commands/recreate_index.py +++ b/learning_resources_search/management/commands/recreate_index.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand, CommandError -from learning_resources_search.constants import ALL_INDEX_TYPES +from learning_resources_search.constants import ALL_INDEX_TYPES, COMBINED_INDEX from learning_resources_search.indexing_api import get_existing_reindexing_indexes from learning_resources_search.tasks import start_recreate_index from main.utils import now_in_utc @@ -25,13 +25,21 @@ def add_arguments(self, parser): "--all", dest="all", action="store_true", help="Recreate all indexes" ) + parser.add_argument( + "--combined_hybrid", + dest=COMBINED_INDEX, + action="store_true", + help="Recreate combined index for hybrid search", + ) + for object_type in sorted(ALL_INDEX_TYPES): - parser.add_argument( - f"--{object_type}s", - dest=object_type, - action="store_true", - help=f"Recreate the {object_type} index", - ) + if object_type != COMBINED_INDEX: + parser.add_argument( + f"--{object_type}s", + dest=object_type, + action="store_true", + help=f"Recreate the {object_type} index", + ) super().add_arguments(parser) def handle(self, *args, **options): # noqa: ARG002 @@ -41,7 +49,10 @@ def handle(self, *args, **options): # noqa: ARG002 indexes_to_update = list(ALL_INDEX_TYPES) else: indexes_to_update = list( - filter(lambda object_type: options[object_type], ALL_INDEX_TYPES) + filter( + lambda object_type: options[object_type], + ALL_INDEX_TYPES, + ) ) if not indexes_to_update: self.stdout.write("Must select at least one index to update") diff --git a/learning_resources_search/serializers.py b/learning_resources_search/serializers.py index c38725edef..da7e740e26 100644 --- a/learning_resources_search/serializers.py +++ b/learning_resources_search/serializers.py @@ -132,6 +132,9 @@ def serialize_learning_resource_for_update( serialized_data = LearningResourceSerializer(instance=learning_resource_obj).data + if not (serialized_data.get("description") or "").strip(): + serialized_data["description"] = None + if ( learning_resource_obj.resource_type == LearningResourceType.course.name and Course.objects.filter(learning_resource=learning_resource_obj).exists() @@ -421,9 +424,10 @@ class LearningResourcesSearchRequestSerializer(SearchRequestSerializer): ), ) search_mode_choices = [ + ("phrase", "phrase"), ("best_fields", "best_fields"), ("most_fields", "most_fields"), - ("phrase", "phrase"), + ("hybrid", "hybrid"), ] search_mode = serializers.ChoiceField( required=False, diff --git a/learning_resources_search/tasks.py b/learning_resources_search/tasks.py index 21e7693494..571766e118 100644 --- a/learning_resources_search/tasks.py +++ b/learning_resources_search/tasks.py @@ -34,7 +34,9 @@ gen_content_file_id, percolate_matches_for_document, ) +from learning_resources_search.connection import get_vector_model_id from learning_resources_search.constants import ( + COMBINED_INDEX, CONTENT_FILE_TYPE, COURSE_TYPE, LEARNING_RESOURCE_TYPES, @@ -285,7 +287,7 @@ def send_subscription_emails(self, subscription_type, period="daily"): retry_backoff=True, rate_limit="600/m", ) -def index_learning_resources(ids, resource_type, index_types): +def index_learning_resources(ids, index_name, index_types): """ Index courses @@ -298,7 +300,7 @@ def index_learning_resources(ids, resource_type, index_types): """ try: with wrap_retry_exception(*SEARCH_CONN_EXCEPTIONS): - api.index_learning_resources(ids, resource_type, index_types) + api.index_learning_resources(ids, index_name, index_types) except (RetryError, Ignore): raise except SystemExit as err: @@ -538,7 +540,7 @@ def wrap_retry_exception(*exception_classes): @app.task(bind=True) -def start_recreate_index(self, indexes, remove_existing_reindexing_tags): +def start_recreate_index(self, indexes, remove_existing_reindexing_tags): # noqa: C901 """ Wipe and recreate index and mapping, and index all items. """ @@ -554,6 +556,16 @@ def start_recreate_index(self, indexes, remove_existing_reindexing_tags): log.exception(error) return error + if COMBINED_INDEX in indexes: + vector_model_id = get_vector_model_id() + if not vector_model_id: + log.warning( + "No vector model is configured. Skipping hybrid index reindexing.", + ) + indexes.remove(COMBINED_INDEX) + if len(indexes) == 0: + return None + api.delete_orphaned_indexes( indexes, delete_reindexing_tags=remove_existing_reindexing_tags ) @@ -619,6 +631,24 @@ def start_recreate_index(self, indexes, remove_existing_reindexing_tags): ) ] + if COMBINED_INDEX in indexes: + blocklisted_ids = load_course_blocklist() + + index_tasks = index_tasks + [ + index_learning_resources.si( + ids, + COMBINED_INDEX, + index_types=IndexestoUpdate.reindexing_index.value, + ) + for ids in chunks( + LearningResource.objects.filter(published=True) + .exclude(readable_id=blocklisted_ids) + .order_by("id") + .values_list("id", flat=True), + chunk_size=settings.OPENSEARCH_INDEXING_CHUNK_SIZE, + ) + ] + for resource_type in set(LEARNING_RESOURCE_TYPES) - {COURSE_TYPE}: if resource_type in indexes: index_tasks = index_tasks + [ diff --git a/learning_resources_search/tasks_test.py b/learning_resources_search/tasks_test.py index 1b192eecfb..3e38c93c77 100644 --- a/learning_resources_search/tasks_test.py +++ b/learning_resources_search/tasks_test.py @@ -23,6 +23,7 @@ from learning_resources.views import FeaturedViewSet from learning_resources_search.api import gen_content_file_id from learning_resources_search.constants import ( + COMBINED_INDEX, CONTENT_FILE_TYPE, COURSE_TYPE, LEARNING_RESOURCE_TYPES, @@ -133,42 +134,52 @@ def test_system_exit_retry(mocker): @pytest.mark.parametrize( - "indexes", - [["course"], ["program"], list(LEARNING_RESOURCE_TYPES)], + ("indexes", "model_exists"), + [ + (["course"], False), + (["program"], False), + (["combined_hybrid"], False), + (["combined_hybrid"], True), + ], ) -def test_start_recreate_index(mocker, mocked_celery, user, indexes): +def test_start_recreate_index(mocker, mocked_celery, user, indexes, model_exists): # noqa: PLR0912,PLR0915,C901 """ recreate_index should recreate the OpenSearch index and reindex all data with it """ settings.OPENSEARCH_INDEXING_CHUNK_SIZE = 2 settings.OPENSEARCH_DOCUMENT_INDEXING_CHUNK_SIZE = 2 + model_value = "test_vector_model_id" if model_exists else None + mocker.patch( + "learning_resources_search.tasks.get_vector_model_id", return_value=model_value + ) + mock_blocklist = mocker.patch( "learning_resources_search.tasks.load_course_blocklist", return_value=[] ) - if COURSE_TYPE in indexes: - ocw_courses = sorted( - CourseFactory.create_batch(4, etl_source=ETLSource.ocw.value), - key=lambda course: course.learning_resource_id, - ) + ocw_courses = sorted( + CourseFactory.create_batch(4, etl_source=ETLSource.ocw.value), + key=lambda course: course.learning_resource_id, + ) - for course in ocw_courses: - ContentFileFactory.create_batch( - 3, run=course.learning_resource.runs.first() - ) + for course in ocw_courses: + ContentFileFactory.create_batch(3, run=course.learning_resource.runs.first()) - oll_courses = CourseFactory.create_batch(2, etl_source=ETLSource.ocw.value) + oll_courses = CourseFactory.create_batch(2, etl_source=ETLSource.ocw.value) - courses = sorted( - list(oll_courses) + list(ocw_courses), - key=lambda course: course.learning_resource_id, - ) - else: - programs = sorted( - ProgramFactory.create_batch(4), - key=lambda program: program.learning_resource_id, - ) + courses = sorted( + list(oll_courses) + list(ocw_courses), + key=lambda course: course.learning_resource_id, + ) + + programs = sorted( + ProgramFactory.create_batch( + 4, + courses=[], + ), + key=lambda program: program.learning_resource_id, + ) index_learning_resources_mock = mocker.patch( "learning_resources_search.tasks.index_learning_resources", autospec=True @@ -195,78 +206,98 @@ def test_start_recreate_index(mocker, mocked_celery, user, indexes): "learning_resources_search.tasks.finish_recreate_index", autospec=True ) - with pytest.raises(mocked_celery.replace_exception_class): + finish_recreate_index_dict = {} + + if not model_exists and COMBINED_INDEX in indexes: start_recreate_index.delay(indexes, remove_existing_reindexing_tags=False) + assert create_backing_index_mock.call_count == 0 + assert finish_recreate_index_mock.s.call_count == 0 + assert delete_orphaned_indexes_mock.call_count == 0 + else: + with pytest.raises(mocked_celery.replace_exception_class): + start_recreate_index.delay(indexes, remove_existing_reindexing_tags=False) - finish_recreate_index_dict = {} + for doctype in [COURSE_TYPE, PROGRAM_TYPE, COMBINED_INDEX]: + if doctype in indexes: + finish_recreate_index_dict[doctype] = backing_index + create_backing_index_mock.assert_any_call(doctype) - delete_orphaned_indexes_mock.assert_called_once_with( - indexes, delete_reindexing_tags=False - ) + finish_recreate_index_mock.s.assert_called_once_with(finish_recreate_index_dict) - for doctype in LEARNING_RESOURCE_TYPES: - if doctype in indexes: - finish_recreate_index_dict[doctype] = backing_index - create_backing_index_mock.assert_any_call(doctype) + delete_orphaned_indexes_mock.assert_called_once_with( + indexes, delete_reindexing_tags=False + ) - finish_recreate_index_mock.s.assert_called_once_with(finish_recreate_index_dict) - assert mocked_celery.group.call_count == 1 + assert mocked_celery.group.call_count == 1 - # Celery's 'group' function takes a generator as an argument. In order to make assertions about the items - # in that generator, 'list' is being called to force iteration through all of those items. - list(mocked_celery.group.call_args[0][0]) + # Celery's 'group' function takes a generator as an argument. In order to make assertions about the items + # in that generator, 'list' is being called to force iteration through all of those items. + list(mocked_celery.group.call_args[0][0]) - if COURSE_TYPE in indexes: - mock_blocklist.assert_called_once() - assert index_learning_resources_mock.si.call_count == 3 - index_learning_resources_mock.si.assert_any_call( - [courses[0].learning_resource_id, courses[1].learning_resource_id], - COURSE_TYPE, - index_types=IndexestoUpdate.reindexing_index.value, - ) - index_learning_resources_mock.si.assert_any_call( - [courses[2].learning_resource_id, courses[3].learning_resource_id], - COURSE_TYPE, - index_types=IndexestoUpdate.reindexing_index.value, - ) - index_learning_resources_mock.si.assert_any_call( - [courses[4].learning_resource_id, courses[5].learning_resource_id], - COURSE_TYPE, - index_types=IndexestoUpdate.reindexing_index.value, - ) + if COURSE_TYPE in indexes: + assert index_learning_resources_mock.si.call_count == 3 - for course in ocw_courses: - content_file_ids = ( - course.learning_resource.runs.first() - .content_files.order_by("id") - .values_list("id", flat=True) + if PROGRAM_TYPE in indexes: + assert index_learning_resources_mock.si.call_count == 2 + + if COMBINED_INDEX in indexes and model_exists: + assert index_learning_resources_mock.si.call_count == 5 + + if COMBINED_INDEX in indexes and not model_exists: + assert index_learning_resources_mock.si.call_count == 0 + + if COURSE_TYPE in indexes or (COMBINED_INDEX in indexes and model_exists): + mock_blocklist.assert_called_once() + index_type = indexes[0] + index_learning_resources_mock.si.assert_any_call( + [courses[0].learning_resource_id, courses[1].learning_resource_id], + index_type, + index_types=IndexestoUpdate.reindexing_index.value, ) - index_files_mock.si.assert_any_call( - [content_file_ids[0], content_file_ids[1]], - course.learning_resource_id, + index_learning_resources_mock.si.assert_any_call( + [courses[2].learning_resource_id, courses[3].learning_resource_id], + index_type, index_types=IndexestoUpdate.reindexing_index.value, ) - - index_files_mock.si.assert_any_call( - [content_file_ids[2]], - course.learning_resource_id, + index_learning_resources_mock.si.assert_any_call( + [courses[4].learning_resource_id, courses[5].learning_resource_id], + index_type, index_types=IndexestoUpdate.reindexing_index.value, ) - if indexes == [PROGRAM_TYPE]: - assert index_learning_resources_mock.si.call_count == 2 - index_learning_resources_mock.si.assert_any_call( - [programs[0].learning_resource_id, programs[1].learning_resource_id], - PROGRAM_TYPE, - index_types=IndexestoUpdate.reindexing_index.value, - ) - index_learning_resources_mock.si.assert_any_call( - [programs[2].learning_resource_id, programs[3].learning_resource_id], - PROGRAM_TYPE, - index_types=IndexestoUpdate.reindexing_index.value, - ) - assert mocked_celery.replace.call_count == 1 - assert mocked_celery.replace.call_args[0][1] == mocked_celery.chain.return_value + if COURSE_TYPE in indexes: + for course in ocw_courses: + content_file_ids = ( + course.learning_resource.runs.first() + .content_files.order_by("id") + .values_list("id", flat=True) + ) + index_files_mock.si.assert_any_call( + [content_file_ids[0], content_file_ids[1]], + course.learning_resource_id, + index_types=IndexestoUpdate.reindexing_index.value, + ) + + index_files_mock.si.assert_any_call( + [content_file_ids[2]], + course.learning_resource_id, + index_types=IndexestoUpdate.reindexing_index.value, + ) + + if PROGRAM_TYPE in indexes or (COMBINED_INDEX in indexes and model_exists): + index_type = indexes[0] + index_learning_resources_mock.si.assert_any_call( + [programs[0].learning_resource_id, programs[1].learning_resource_id], + index_type, + index_types=IndexestoUpdate.reindexing_index.value, + ) + index_learning_resources_mock.si.assert_any_call( + [programs[2].learning_resource_id, programs[3].learning_resource_id], + index_type, + index_types=IndexestoUpdate.reindexing_index.value, + ) + assert mocked_celery.replace.call_count == 1 + assert mocked_celery.replace.call_args[0][1] == mocked_celery.chain.return_value @pytest.mark.parametrize( diff --git a/main/settings.py b/main/settings.py index 02ecb219c3..16ae7d841e 100644 --- a/main/settings.py +++ b/main/settings.py @@ -604,6 +604,16 @@ OPENSEARCH_MAX_SUGGEST_HITS = get_int("OPENSEARCH_MAX_SUGGEST_HITS", 1) OPENSEARCH_MAX_SUGGEST_RESULTS = get_int("OPENSEARCH_MAX_SUGGEST_RESULTS", 1) OPENSEARCH_SHARD_COUNT = get_int("OPENSEARCH_SHARD_COUNT", 2) +OPENSEARCH_VECTOR_MODEL_NAME = get_string( + "OPENSEARCH_VECTOR_MODEL_NAME", + "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b", +) +OPENSEARCH_VECTOR_MODEL_NAME_VERSION = get_string( + "OPENSEARCH_VECTOR_MODEL_NAME_VERSION", "1.0.3" +) +OPENSEARCH_VECTOR_MODEL_FORMAT = get_string( + "OPENSEARCH_VECTOR_MODEL_FORMAT", "TORCH_SCRIPT" +) OPENSEARCH_REPLICA_COUNT = get_int("OPENSEARCH_REPLICA_COUNT", 2) OPENSEARCH_MAX_REQUEST_SIZE = get_int("OPENSEARCH_MAX_REQUEST_SIZE", 10485760) INDEXING_ERROR_RETRIES = get_int("INDEXING_ERROR_RETRIES", 1) diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index cb26bd097b..59c2904dde 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -4371,15 +4371,16 @@ paths: name: search_mode schema: enum: + - phrase - best_fields - most_fields - - phrase + - hybrid type: string minLength: 1 description: "The open search search type for text queries \n\n\ - * `best_fields` - best_fields\n* `most_fields` - most_fields\n* `phrase`\ - \ - phrase\n\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ - * `phrase` - phrase" + * `phrase` - phrase\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ + * `hybrid` - hybrid\n\n* `phrase` - phrase\n* `best_fields` - best_fields\n\ + * `most_fields` - most_fields\n* `hybrid` - hybrid" - in: query name: slop schema: @@ -4877,15 +4878,16 @@ paths: name: search_mode schema: enum: + - phrase - best_fields - most_fields - - phrase + - hybrid type: string minLength: 1 description: "The open search search type for text queries \n\n\ - * `best_fields` - best_fields\n* `most_fields` - most_fields\n* `phrase`\ - \ - phrase\n\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ - * `phrase` - phrase" + * `phrase` - phrase\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ + * `hybrid` - hybrid\n\n* `phrase` - phrase\n* `best_fields` - best_fields\n\ + * `most_fields` - most_fields\n* `hybrid` - hybrid" - in: query name: slop schema: @@ -5408,15 +5410,16 @@ paths: name: search_mode schema: enum: + - phrase - best_fields - most_fields - - phrase + - hybrid type: string minLength: 1 description: "The open search search type for text queries \n\n\ - * `best_fields` - best_fields\n* `most_fields` - most_fields\n* `phrase`\ - \ - phrase\n\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ - * `phrase` - phrase" + * `phrase` - phrase\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ + * `hybrid` - hybrid\n\n* `phrase` - phrase\n* `best_fields` - best_fields\n\ + * `most_fields` - most_fields\n* `hybrid` - hybrid" - in: query name: slop schema: @@ -5930,15 +5933,16 @@ paths: name: search_mode schema: enum: + - phrase - best_fields - most_fields - - phrase + - hybrid type: string minLength: 1 description: "The open search search type for text queries \n\n\ - * `best_fields` - best_fields\n* `most_fields` - most_fields\n* `phrase`\ - \ - phrase\n\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ - * `phrase` - phrase" + * `phrase` - phrase\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ + * `hybrid` - hybrid\n\n* `phrase` - phrase\n* `best_fields` - best_fields\n\ + * `most_fields` - most_fields\n* `hybrid` - hybrid" - in: query name: slop schema: @@ -13246,9 +13250,9 @@ components: allOf: - $ref: '#/components/schemas/SearchModeEnum' description: "The open search search type for text queries \n\ - \n* `best_fields` - best_fields\n* `most_fields` - most_fields\n* `phrase`\ - \ - phrase\n\n* `best_fields` - best_fields\n* `most_fields` - most_fields\n\ - * `phrase` - phrase" + \n* `phrase` - phrase\n* `best_fields` - best_fields\n* `most_fields`\ + \ - most_fields\n* `hybrid` - hybrid\n\n* `phrase` - phrase\n* `best_fields`\ + \ - best_fields\n* `most_fields` - most_fields\n* `hybrid` - hybrid" slop: type: integer nullable: true @@ -14907,22 +14911,26 @@ components: - title SearchModeEnum: enum: + - phrase - best_fields - most_fields - - phrase + - hybrid type: string description: |- + * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields - * `phrase` - phrase + * `hybrid` - hybrid + * `phrase` - phrase * `best_fields` - best_fields * `most_fields` - most_fields - * `phrase` - phrase + * `hybrid` - hybrid x-enum-descriptions: + - phrase - best_fields - most_fields - - phrase + - hybrid SortbyEnum: enum: - featured diff --git a/poetry.lock b/poetry.lock index 6da2843fe2..7e61935b0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1049,6 +1049,98 @@ files = [ test = ["PyYAML", "mock", "pytest"] yaml = ["PyYAML"] +[[package]] +name = "contourpy" +version = "1.3.3" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, +] + +[package.dependencies] +numpy = ">=1.25" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" version = "7.8.0" @@ -1267,6 +1359,22 @@ files = [ {file = "cwcwidth-0.1.10.tar.gz", hash = "sha256:7468760f72c1f4107be1b2b2854bc000401ea36a69daed36fb966a1e19a7a124"}, ] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "dataclasses-json" version = "0.6.7" @@ -2225,6 +2333,87 @@ files = [ {file = "flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e"}, ] +[[package]] +name = "fonttools" +version = "4.60.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, + {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, + {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, + {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, + {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, + {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, + {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, + {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, + {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, + {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, + {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, + {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, + {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, + {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, + {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, + {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, + {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + [[package]] name = "freezegun" version = "1.5.1" @@ -3542,6 +3731,117 @@ files = [ cryptography = ">=3.4" typing-extensions = ">=4.5.0" +[[package]] +name = "kiwisolver" +version = "1.4.9" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, +] + [[package]] name = "kombu" version = "5.5.3" @@ -4255,7 +4555,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -4383,6 +4683,85 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] tests = ["pytest", "simplejson"] +[[package]] +name = "matplotlib" +version = "3.10.7" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, + {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42"}, + {file = "matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7"}, + {file = "matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1"}, + {file = "matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695"}, + {file = "matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632"}, + {file = "matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1"}, + {file = "matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca"}, + {file = "matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91"}, + {file = "matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=3" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "matplotlib-inline" version = "0.1.7" @@ -4404,7 +4783,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -4948,67 +5327,86 @@ six = ">=1.9.0" [[package]] name = "numpy" -version = "2.2.5" +version = "2.3.5" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba"}, - {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3"}, - {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57"}, - {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c"}, - {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1"}, - {file = "numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88"}, - {file = "numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54"}, - {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610"}, - {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b"}, - {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be"}, - {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906"}, - {file = "numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175"}, - {file = "numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa"}, - {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571"}, - {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073"}, - {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8"}, - {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae"}, - {file = "numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb"}, - {file = "numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191"}, - {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372"}, - {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d"}, - {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7"}, - {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73"}, - {file = "numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b"}, - {file = "numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376"}, - {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19"}, - {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0"}, - {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a"}, - {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066"}, - {file = "numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e"}, - {file = "numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169"}, - {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748"}, + {file = "numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c"}, + {file = "numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c"}, + {file = "numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c"}, + {file = "numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952"}, + {file = "numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa"}, + {file = "numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce"}, + {file = "numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e"}, + {file = "numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b"}, + {file = "numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1"}, + {file = "numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3"}, + {file = "numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234"}, + {file = "numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8"}, + {file = "numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248"}, + {file = "numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e"}, + {file = "numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227"}, + {file = "numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5"}, + {file = "numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf"}, + {file = "numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425"}, + {file = "numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0"}, ] [[package]] @@ -5155,6 +5553,32 @@ develop = ["black (>=24.3.0)", "botocore", "coverage (<8.0.0)", "jinja2", "myst_ docs = ["aiohttp (>=3.9.4,<4)", "myst_parser", "sphinx", "sphinx_copybutton", "sphinx_rtd_theme"] kerberos = ["requests_kerberos"] +[[package]] +name = "opensearch-py-ml" +version = "1.3.0" +description = "Python Client and Toolkit for DataFrames, Big Data, Machine Learning and ETL in OpenSearch" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "opensearch_py_ml-1.3.0-py3-none-any.whl", hash = "sha256:9ae112dfd8fedc734f685d3e7cde00c4d8ffe5b03b2b7c787a6baf456565238f"}, + {file = "opensearch_py_ml-1.3.0.tar.gz", hash = "sha256:acb77b69854136ab93d7db8421317e838591e89417aeef13bff076edf547d637"}, +] + +[package.dependencies] +boto3 = ">=1.26.0" +botocore = ">=1.29.0" +colorama = ">=0.4.6" +deprecated = ">=1.2.14,<2" +matplotlib = ">=3.6.0,<4" +numpy = ">=2.3.2" +opensearch-py = ">=2" +pandas = ">=1.5.2,<2.1.0 || >2.1.0,<2.3" +pyyaml = ">=6.0.2" +requests = ">=2.28.0" +requests-aws4auth = ">=1.1.0" +rich = ">=13.5.2" + [[package]] name = "opentelemetry-api" version = "1.33.0" @@ -7434,6 +7858,24 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-aws4auth" +version = "1.3.1" +description = "AWS4 authentication for Requests" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "requests_aws4auth-1.3.1-py3-none-any.whl", hash = "sha256:2969b5379ae6e60ee666638caf6cb94a32d67033f6bfcf0d50c95cd5474f2419"}, + {file = "requests_aws4auth-1.3.1.tar.gz", hash = "sha256:b6ad4882310e03ba2538ebf94d1f001ca9feabc5c52618539cf1eb6d5af76791"}, +] + +[package.dependencies] +requests = "*" + +[package.extras] +httpx = ["httpx"] + [[package]] name = "requests-file" version = "2.1.0" @@ -7523,7 +7965,7 @@ version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, @@ -9344,4 +9786,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "~3.12" -content-hash = "fcfdd6b556b0d7b9973ad98960e02869ba1616878a7e1799fa94a84763a61ca5" +content-hash = "a6a8fce5ca01b42adb4044c60ae3abdec210d439c8fbd573ba63b91b82927867" diff --git a/pyproject.toml b/pyproject.toml index fefb17ee84..faabacf2a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,6 +115,7 @@ youtube-transcript-api = "^1.0.0" pypdfium2 = "^4.30.0" pyarrow = "^21.0.0" django-zeal = "^2.0.4" +opensearch-py-ml = "^1.3.0"