bertrend 0.3.4__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bertrend-0.3.4 → bertrend-0.3.5}/PKG-INFO +5 -4
- {bertrend-0.3.4 → bertrend-0.3.5}/README.md +1 -1
- bertrend-0.3.5/bertrend/services/embedding_server/README.md +31 -0
- bertrend-0.3.5/bertrend/services/embedding_server/config/default_config.toml +7 -0
- bertrend-0.3.5/bertrend/services/embedding_server/config/settings.py +34 -0
- bertrend-0.3.5/bertrend/services/embedding_server/main.py +14 -0
- bertrend-0.3.5/bertrend/services/embedding_server/models.py +12 -0
- bertrend-0.3.5/bertrend/services/embedding_server/routers/authentication.py +45 -0
- bertrend-0.3.5/bertrend/services/embedding_server/routers/embeddings.py +39 -0
- bertrend-0.3.5/bertrend/services/embedding_server/routers/info.py +29 -0
- bertrend-0.3.5/bertrend/services/embedding_server/security.py +368 -0
- bertrend-0.3.5/bertrend/services/embedding_server/start.py +26 -0
- bertrend-0.3.5/bertrend/services/embedding_server/status.py +30 -0
- bertrend-0.3.5/bertrend/services/embedding_server/stop.py +14 -0
- bertrend-0.3.5/bertrend/tests/test_installation.py +103 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/data_structure.py +1 -1
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/prompts.py +84 -7
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/weak_signals.py +1 -1
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/data_loading.py +4 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/common/crontab_utils.py +28 -3
- bertrend-0.3.5/bertrend_apps/exploration/curebot/__init__.py +4 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/app_utils.py +11 -3
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/start_newsletter_generator.sh +1 -1
- bertrend-0.3.5/bertrend_apps/newsletters/__init__.py +4 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/app.py +2 -2
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_config.py +60 -26
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_data.py +17 -11
- bertrend-0.3.5/bertrend_apps/prospective_demo/i18n_translations.py +380 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/models_info.py +77 -54
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_generation.py +25 -24
- {bertrend-0.3.4 → bertrend-0.3.5}/pyproject.toml +4 -3
- bertrend-0.3.4/bertrend/tests/test_installation.py +0 -181
- bertrend-0.3.4/bertrend_apps/exploration/curebot/old_veille_analyse.py +0 -298
- bertrend-0.3.4/bertrend_apps/prospective_demo/i18n_translations.py +0 -91
- {bertrend-0.3.4 → bertrend-0.3.5}/AUTHORS.txt +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/LICENSE.md +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/BERTopicModel.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/BERTrend.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/bertopic_default_config.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/bertrend_default_config.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/parameters.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/services_default_config.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/data_loading_component.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/embed_documents_component.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/i18n.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/i18n_translations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/icons.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/parameters_component.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/state_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_summarizer_app.sh +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_topic_analysis_app.sh +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_weak_signals_app.sh +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/summarization/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/summarization/summarizer_app.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/app.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/app_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/explore_topics.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/training_page.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/i18n_translations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/app.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/i18n_translations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/visualizations_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/newsletter.css +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/newsletter_features.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/openai_client.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/prompts.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/temporal_metrics_embedding.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/topic_metrics.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/common_ngrams.json +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-en.json +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-fr.json +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-rte.json +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/authentication.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/embedding_client.py +0 -0
- {bertrend-0.3.4/bertrend/services/summary → bertrend-0.3.5/bertrend/services/embedding_server}/__init__.py +0 -0
- {bertrend-0.3.4/bertrend/tests → bertrend-0.3.5/bertrend/services/embedding_server/routers}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/embedding_service.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summarizer.py +0 -0
- {bertrend-0.3.4/bertrend/topic_analysis → bertrend-0.3.5/bertrend/services/summary}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/abstractive_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/chatgpt_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/extractive_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/lexrank.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/prompts.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/README_integration_tests.md +0 -0
- {bertrend-0.3.4/bertrend/trend_analysis → bertrend-0.3.5/bertrend/tests}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/conftest.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_abstractive_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_bertrend.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_cache_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_chatgpt_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_config_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_data_loading.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_data_structure.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_embedding_client.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_extractive_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_integration.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_newsletter_features.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_openai_client.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_representative_docs.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_summarizer.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_temporal_metrics_embedding.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_description.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_metrics.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_model.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_data_structure.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_prompts.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_visualizations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_weak_signals.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_utils_init.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_visualizations.py +0 -0
- {bertrend-0.3.4/bertrend_apps → bertrend-0.3.5/bertrend/topic_analysis}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/data_structure.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/prompts.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/representative_docs.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/topic_description.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/visualizations.py +0 -0
- {bertrend-0.3.4/bertrend_apps/common → bertrend-0.3.5/bertrend/trend_analysis}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/signal_llm_template_en.html +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/signal_llm_template_fr.html +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/visualizations.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/cache_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/config_utils.py +0 -0
- {bertrend-0.3.4/bertrend_apps/exploration → bertrend-0.3.5/bertrend_apps}/__init__.py +0 -0
- {bertrend-0.3.4/bertrend_apps/exploration/curebot → bertrend-0.3.5/bertrend_apps/common}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/common/mail_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/arxiv_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/decarbonation_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/llm_en_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/llm_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/nlp_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/rte_feed.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/gmail_credentials.json +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/__main__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/arxiv_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/atom_feed_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/bing_news_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/data_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/google_news_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/newscatcher_provider.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/utils.py +0 -0
- {bertrend-0.3.4/bertrend_apps/newsletters → bertrend-0.3.5/bertrend_apps/exploration}/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/app.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/config.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/import_extraction_article.ipynb +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/prompts.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab1.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab2.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab3.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/Main_page.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/entity_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/common_factory.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/nlp.config +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/install_feeds_and_newsletters.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/newsletters/__main__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/.streamlit/config.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/.streamlit/secrets.toml +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/__init__.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/authentication.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_analysis.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_common.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_signals.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_common.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/llm_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/perf_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/process_new_data.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_template.html +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/start_demo.sh +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/streamlit_utils.py +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/start_apps.sh +0 -0
- {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/stop_apps.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bertrend
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: BERTrend analyses topic evolution over time using state-of-the-art transformer models: topic modelling and LLM
|
|
5
5
|
License: LICENSE.md
|
|
6
6
|
Keywords: topics,BERTopic,transformers,weak signals,LLM
|
|
@@ -11,6 +11,7 @@ Maintainer-email: jerome.picault@rte-france.com
|
|
|
11
11
|
Requires-Python: >=3.12,<4.0
|
|
12
12
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
13
|
Provides-Extra: apps
|
|
14
|
+
Requires-Dist: PyJWT (>=2.10.1)
|
|
14
15
|
Requires-Dist: Pyqtree (==1.0.0)
|
|
15
16
|
Requires-Dist: arxiv (>=2.2.0) ; extra == "apps"
|
|
16
17
|
Requires-Dist: bertopic (==0.16.2)
|
|
@@ -48,7 +49,7 @@ Requires-Dist: plotly (>=5.24.1)
|
|
|
48
49
|
Requires-Dist: plotly-resampler (>=0.10.0)
|
|
49
50
|
Requires-Dist: pygooglenews (>=0.1.3) ; extra == "apps"
|
|
50
51
|
Requires-Dist: pylabeladjust (==0.1.13)
|
|
51
|
-
Requires-Dist: pytest (>=8.
|
|
52
|
+
Requires-Dist: pytest (>=8.4.0)
|
|
52
53
|
Requires-Dist: requests (>=2.32.3)
|
|
53
54
|
Requires-Dist: scikit-learn (>=1.5.2)
|
|
54
55
|
Requires-Dist: scipy (==1.12)
|
|
@@ -56,7 +57,7 @@ Requires-Dist: seaborn (>=0.13.2)
|
|
|
56
57
|
Requires-Dist: sentence-transformers (==3.0.1)
|
|
57
58
|
Requires-Dist: sentencepiece (>=0.2.0)
|
|
58
59
|
Requires-Dist: starlette (>=0.46.2)
|
|
59
|
-
Requires-Dist: streamlit (>=1.45.
|
|
60
|
+
Requires-Dist: streamlit (>=1.45.1)
|
|
60
61
|
Requires-Dist: streamlit-code-editor (>=0.1.22)
|
|
61
62
|
Requires-Dist: thefuzz (>=0.22.1)
|
|
62
63
|
Requires-Dist: tiktoken (>=0.8.0)
|
|
@@ -259,7 +260,7 @@ We recommend using a remote embedding service (this is now the default config) a
|
|
|
259
260
|
|
|
260
261
|
The change of configuration can be done in: [services_default_config.toml](bertrend/config/services_default_config.toml).
|
|
261
262
|
|
|
262
|
-
An example of a very simple implementation to deploy a separate embedding service can be found here:
|
|
263
|
+
An example of a very simple implementation to deploy a separate embedding service can be found here: `bertrend/services/embedding_server`
|
|
263
264
|
|
|
264
265
|
|
|
265
266
|

|
|
@@ -186,7 +186,7 @@ We recommend using a remote embedding service (this is now the default config) a
|
|
|
186
186
|
|
|
187
187
|
The change of configuration can be done in: [services_default_config.toml](bertrend/config/services_default_config.toml).
|
|
188
188
|
|
|
189
|
-
An example of a very simple implementation to deploy a separate embedding service can be found here:
|
|
189
|
+
An example of a very simple implementation to deploy a separate embedding service can be found here: `bertrend/services/embedding_server`
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|

|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# How to use the embedding server
|
|
2
|
+
|
|
3
|
+
## Launch the embedding server
|
|
4
|
+
|
|
5
|
+
- check the file `config/default_config.toml`
|
|
6
|
+
|
|
7
|
+
- run the embedding server
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
python start.py
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Check the embedding server
|
|
14
|
+
Ensure the API is launched.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from bertrend.services.embedding_client import EmbeddingAPIClient
|
|
18
|
+
|
|
19
|
+
embedding_service_endpoint = "https://yourservice:1234"
|
|
20
|
+
|
|
21
|
+
# Initialize the API
|
|
22
|
+
api = EmbeddingAPIClient(embedding_service_endpoint)
|
|
23
|
+
|
|
24
|
+
# Get embedding model name
|
|
25
|
+
print(api.get_api_model_name())
|
|
26
|
+
|
|
27
|
+
# Transform text to embedding
|
|
28
|
+
text = "Hello, world!"
|
|
29
|
+
embedding = api.embed_query(text)
|
|
30
|
+
print(embedding)
|
|
31
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import tomllib
|
|
9
|
+
from pydantic_settings import BaseSettings
|
|
10
|
+
|
|
11
|
+
DEFAULT_CONFIG_FILE = Path(__file__).parent / "default_config.toml"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EmbeddingAPIConfig(BaseSettings):
|
|
15
|
+
host: str
|
|
16
|
+
port: int
|
|
17
|
+
model_name: str
|
|
18
|
+
number_workers: int
|
|
19
|
+
cuda_visible_devices: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Use environment variable to specify which config to load
|
|
23
|
+
def get_config() -> EmbeddingAPIConfig:
|
|
24
|
+
"""
|
|
25
|
+
Return the configuration for the embedding API.
|
|
26
|
+
The configuration is loaded from a toml file.
|
|
27
|
+
The path to the toml file can be specified using the environment variable `EMBEDDING_API_CONFIG_FILE`.
|
|
28
|
+
If the environment variable is not set, the default configuration file is used.
|
|
29
|
+
"""
|
|
30
|
+
config_file = os.environ.get("EMBEDDING_API_CONFIG_FILE", None)
|
|
31
|
+
if config_file is None:
|
|
32
|
+
config_file = DEFAULT_CONFIG_FILE
|
|
33
|
+
with open(config_file, "rb") as f:
|
|
34
|
+
return EmbeddingAPIConfig(**tomllib.load(f))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
|
|
8
|
+
from bertrend.services.embedding_server.routers import embeddings, info, authentication
|
|
9
|
+
|
|
10
|
+
app = FastAPI()
|
|
11
|
+
|
|
12
|
+
app.include_router(info.router, tags=["Info"])
|
|
13
|
+
app.include_router(embeddings.router, tags=["Embedding"])
|
|
14
|
+
app.include_router(authentication.router, tags=["Authentication"])
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
# Pydantic class for FastAPI typing control
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputText(BaseModel):
|
|
11
|
+
text: str | list[str]
|
|
12
|
+
show_progress_bar: bool
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
from fastapi import Depends, APIRouter, Security
|
|
8
|
+
from fastapi.security import OAuth2PasswordRequestForm
|
|
9
|
+
|
|
10
|
+
from bertrend.services.embedding_server.security import (
|
|
11
|
+
ADMIN,
|
|
12
|
+
Token,
|
|
13
|
+
TokenData,
|
|
14
|
+
get_token,
|
|
15
|
+
list_registered_clients,
|
|
16
|
+
get_current_client,
|
|
17
|
+
view_rate_limits,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.post("/token")
|
|
24
|
+
async def login_for_access_token(
|
|
25
|
+
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
|
26
|
+
) -> Token:
|
|
27
|
+
return get_token(form_data)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@router.get(
|
|
31
|
+
"/list_registered_clients", summary="List registered clients (requires admin scope)"
|
|
32
|
+
)
|
|
33
|
+
async def list_clients(
|
|
34
|
+
current_client: TokenData = Security(get_current_client, scopes=[ADMIN])
|
|
35
|
+
):
|
|
36
|
+
return list_registered_clients()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get(
|
|
40
|
+
"/rate-limits", summary="Check current rate limit usage (requires admin scope)"
|
|
41
|
+
)
|
|
42
|
+
async def get_rate_limits(
|
|
43
|
+
current_client: TokenData = Security(get_current_client, scopes=[ADMIN]),
|
|
44
|
+
):
|
|
45
|
+
return view_rate_limits()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Security
|
|
7
|
+
from loguru import logger
|
|
8
|
+
from sentence_transformers import SentenceTransformer
|
|
9
|
+
|
|
10
|
+
from bertrend.services.embedding_server.security import (
|
|
11
|
+
TokenData,
|
|
12
|
+
get_current_client,
|
|
13
|
+
FULL_ACCESS,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from bertrend.services.embedding_server.config.settings import get_config
|
|
17
|
+
from bertrend.services.embedding_server.models import InputText
|
|
18
|
+
|
|
19
|
+
# Load the configuration
|
|
20
|
+
CONFIG = get_config()
|
|
21
|
+
|
|
22
|
+
# Load embedding model
|
|
23
|
+
logger.debug(f"Loading embedding model : {CONFIG.model_name}")
|
|
24
|
+
EMBEDDING_MODEL = SentenceTransformer(CONFIG.model_name, trust_remote_code=True)
|
|
25
|
+
|
|
26
|
+
# Fix max model length error
|
|
27
|
+
if EMBEDDING_MODEL.max_seq_length == 514:
|
|
28
|
+
EMBEDDING_MODEL.max_seq_length = 512
|
|
29
|
+
router = APIRouter()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.post("/encode", summary="Embed data (requires full_access scope)")
|
|
33
|
+
def embed(
|
|
34
|
+
input: InputText,
|
|
35
|
+
current_client: TokenData = Security(get_current_client, scopes=[FULL_ACCESS]),
|
|
36
|
+
):
|
|
37
|
+
logger.debug(f"Request by: {current_client.client_id}")
|
|
38
|
+
emb = EMBEDDING_MODEL.encode(input.text, show_progress_bar=input.show_progress_bar)
|
|
39
|
+
return {"embeddings": emb.tolist()}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter
|
|
7
|
+
|
|
8
|
+
from bertrend.services.embedding_server.config.settings import get_config
|
|
9
|
+
|
|
10
|
+
# Load the configuration
|
|
11
|
+
CONFIG = get_config()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/health")
|
|
18
|
+
def health():
|
|
19
|
+
return {"status": "ok"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get("/model_name")
|
|
23
|
+
def get_model_name():
|
|
24
|
+
return CONFIG.model_name
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get("/num_workers")
|
|
28
|
+
def get_num_workers():
|
|
29
|
+
return CONFIG.number_workers
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import secrets
|
|
3
|
+
import time
|
|
4
|
+
from collections import defaultdict, deque
|
|
5
|
+
|
|
6
|
+
import urllib3
|
|
7
|
+
from fastapi import Depends, HTTPException, status, Request
|
|
8
|
+
from fastapi.security import (
|
|
9
|
+
OAuth2PasswordRequestForm,
|
|
10
|
+
SecurityScopes,
|
|
11
|
+
)
|
|
12
|
+
from fastapi.security.oauth2 import OAuth2PasswordBearer
|
|
13
|
+
import jwt # PyJWT
|
|
14
|
+
from datetime import datetime, timedelta, timezone
|
|
15
|
+
from typing import Annotated, Deque
|
|
16
|
+
|
|
17
|
+
from jwt import InvalidTokenError
|
|
18
|
+
from loguru import logger
|
|
19
|
+
from pydantic import BaseModel, ValidationError
|
|
20
|
+
import os
|
|
21
|
+
from dotenv import load_dotenv
|
|
22
|
+
|
|
23
|
+
# Load environment variables
|
|
24
|
+
load_dotenv()
|
|
25
|
+
|
|
26
|
+
# Disable warnings related to https certificates
|
|
27
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_hex_token(length: int = 32):
|
|
31
|
+
"""Generates a random hexadecimal string of the specified length."""
|
|
32
|
+
return secrets.token_hex(length)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Configuration (should be in environment variables)
|
|
36
|
+
# to get a string like this run:
|
|
37
|
+
# openssl rand -hex 32
|
|
38
|
+
SECRET_KEY = os.getenv("BERTREND_SECRET_KEY", generate_hex_token())
|
|
39
|
+
ALGORITHM = "HS256"
|
|
40
|
+
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 48 # 48h
|
|
41
|
+
|
|
42
|
+
# Rate limiting configuration
|
|
43
|
+
DEFAULT_RATE_LIMIT = int(os.getenv("DEFAULT_RATE_LIMIT", "50")) # Requests per minute
|
|
44
|
+
DEFAULT_RATE_WINDOW = int(os.getenv("DEFAULT_RATE_WINDOW", "60")) # Window in seconds
|
|
45
|
+
|
|
46
|
+
# Client registry - in production, store somewhere else - database, file...
|
|
47
|
+
CLIENT_REGISTRY_FILE = os.getenv(
|
|
48
|
+
"CLIENT_REGISTRY_FILE", f"{os.path.expanduser('~')}/bertrend_client_registry.json"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
RESTRICTED_ACCESS = "restricted_access"
|
|
52
|
+
FULL_ACCESS = "full_access"
|
|
53
|
+
ADMIN = "admin"
|
|
54
|
+
|
|
55
|
+
# Define available scopes with descriptions
|
|
56
|
+
SCOPES = {
|
|
57
|
+
RESTRICTED_ACCESS: "Access limited to some endpoints",
|
|
58
|
+
FULL_ACCESS: "Full access to endpoints except admin endpoints",
|
|
59
|
+
ADMIN: "Admin access",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
DEFAULT_CLIENT_REGISTRY = {
|
|
64
|
+
"admin": {
|
|
65
|
+
"client_secret": generate_hex_token(),
|
|
66
|
+
"scopes": [ADMIN, FULL_ACCESS, RESTRICTED_ACCESS],
|
|
67
|
+
"authorized_groups": [], # Empty list means access to all groups
|
|
68
|
+
"rate_limit": DEFAULT_RATE_LIMIT * 2, # Higher limit for admin
|
|
69
|
+
"rate_window": DEFAULT_RATE_WINDOW,
|
|
70
|
+
},
|
|
71
|
+
"bertrend": {
|
|
72
|
+
"client_secret": generate_hex_token(),
|
|
73
|
+
"scopes": [RESTRICTED_ACCESS],
|
|
74
|
+
"authorized_groups": [], # Empty list means access to all groups
|
|
75
|
+
"rate_limit": DEFAULT_RATE_LIMIT,
|
|
76
|
+
"rate_window": DEFAULT_RATE_WINDOW,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Configure OAuth2
|
|
81
|
+
oauth2_scheme = OAuth2PasswordBearer(
|
|
82
|
+
tokenUrl="token",
|
|
83
|
+
scopes=SCOPES,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Store for rate limiting (in memory - for production use Redis or similar)
|
|
87
|
+
# Maps client_id -> deque of request timestamps
|
|
88
|
+
rate_limit_store: dict[str, Deque[float]] = defaultdict(lambda: deque(maxlen=1000))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_client_registry():
|
|
92
|
+
"""Function to load client registry from JSON file"""
|
|
93
|
+
try:
|
|
94
|
+
with open(CLIENT_REGISTRY_FILE, "r") as f:
|
|
95
|
+
return json.load(f)
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
# Create a default client registry file if it doesn't exist
|
|
98
|
+
default_registry = DEFAULT_CLIENT_REGISTRY
|
|
99
|
+
with open(CLIENT_REGISTRY_FILE, "w") as f:
|
|
100
|
+
json.dump(default_registry, f, indent=4)
|
|
101
|
+
return default_registry
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
raise HTTPException(
|
|
104
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
105
|
+
detail="Client registry file is not valid JSON",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def list_registered_clients():
|
|
110
|
+
"""View registered clients (without exposing secrets)"""
|
|
111
|
+
client_registry = load_client_registry()
|
|
112
|
+
# Remove sensitive information like client secrets
|
|
113
|
+
clients_info = {}
|
|
114
|
+
for client_id, client_data in client_registry.items():
|
|
115
|
+
clients_info[client_id] = {
|
|
116
|
+
"scopes": client_data["scopes"],
|
|
117
|
+
"authorized_groups": client_data.get("authorized_groups", []),
|
|
118
|
+
"rate_limit": client_data.get("rate_limit", DEFAULT_RATE_LIMIT),
|
|
119
|
+
"rate_window": client_data.get("rate_window", DEFAULT_RATE_WINDOW),
|
|
120
|
+
}
|
|
121
|
+
return clients_info
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def view_rate_limits():
|
|
125
|
+
"""Return the current usage of the API per client"""
|
|
126
|
+
current_time = time.time()
|
|
127
|
+
usage = {}
|
|
128
|
+
|
|
129
|
+
for client_id, requests in rate_limit_store.items():
|
|
130
|
+
# Count only requests within the window
|
|
131
|
+
client_registry = load_client_registry()
|
|
132
|
+
if client_id in client_registry:
|
|
133
|
+
window = client_registry[client_id].get("rate_window", DEFAULT_RATE_WINDOW)
|
|
134
|
+
cutoff_time = current_time - window
|
|
135
|
+
active_requests = sum(1 for t in requests if t >= cutoff_time)
|
|
136
|
+
|
|
137
|
+
usage[client_id] = {
|
|
138
|
+
"usage": active_requests,
|
|
139
|
+
"limit": client_registry[client_id].get(
|
|
140
|
+
"rate_limit", DEFAULT_RATE_LIMIT
|
|
141
|
+
),
|
|
142
|
+
"window": window,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return usage
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Token(BaseModel):
|
|
149
|
+
access_token: str
|
|
150
|
+
token_type: str
|
|
151
|
+
expires_in: float # This represents the token expiration time in seconds
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TokenData(BaseModel):
|
|
155
|
+
client_id: str | None = None
|
|
156
|
+
scopes: list[str] = []
|
|
157
|
+
authorized_groups: list[str] = []
|
|
158
|
+
rate_limit: int = DEFAULT_RATE_LIMIT
|
|
159
|
+
rate_window: int = DEFAULT_RATE_WINDOW
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
|
163
|
+
"""Creates access token"""
|
|
164
|
+
to_encode = data.copy()
|
|
165
|
+
expire = datetime.now(timezone.utc) + expires_delta or timedelta(
|
|
166
|
+
minutes=ACCESS_TOKEN_EXPIRE_MINUTES
|
|
167
|
+
)
|
|
168
|
+
to_encode.update(
|
|
169
|
+
{
|
|
170
|
+
"exp": expire,
|
|
171
|
+
"expires_in": (
|
|
172
|
+
expires_delta.total_seconds()
|
|
173
|
+
if expires_delta
|
|
174
|
+
else ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
179
|
+
return encoded_jwt
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def check_rate_limit(request: Request, token_data: TokenData):
|
|
183
|
+
"""
|
|
184
|
+
Check if the client has exceeded their rate limit
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
request: The FastAPI request object
|
|
188
|
+
token_data: The TokenData object containing client rate limit info
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
HTTPException: If rate limit is exceeded
|
|
192
|
+
"""
|
|
193
|
+
if token_data.client_id is None:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
client_id = token_data.client_id
|
|
197
|
+
rate_limit = token_data.rate_limit
|
|
198
|
+
rate_window = token_data.rate_window
|
|
199
|
+
|
|
200
|
+
# Record this request
|
|
201
|
+
current_time = time.time()
|
|
202
|
+
client_requests = rate_limit_store[client_id]
|
|
203
|
+
client_requests.append(current_time)
|
|
204
|
+
|
|
205
|
+
# Clean up old requests outside the window
|
|
206
|
+
cutoff_time = current_time - rate_window
|
|
207
|
+
while client_requests and client_requests[0] < cutoff_time:
|
|
208
|
+
client_requests.popleft()
|
|
209
|
+
|
|
210
|
+
# Check if rate limit exceeded
|
|
211
|
+
if len(client_requests) > rate_limit:
|
|
212
|
+
# Calculate reset time
|
|
213
|
+
reset_time = int(client_requests[0] + rate_window - current_time)
|
|
214
|
+
raise HTTPException(
|
|
215
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
216
|
+
detail=f"Rate limit exceeded. Try again in {reset_time} seconds.",
|
|
217
|
+
headers={"Retry-After": str(reset_time), "X-Rate-Limit": str(rate_limit)},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def get_current_client(
|
|
222
|
+
security_scopes: SecurityScopes,
|
|
223
|
+
token: Annotated[str, Depends(oauth2_scheme)],
|
|
224
|
+
request: Request,
|
|
225
|
+
):
|
|
226
|
+
"""Function to verify token and extract client data"""
|
|
227
|
+
if security_scopes.scopes:
|
|
228
|
+
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
|
|
229
|
+
else:
|
|
230
|
+
authenticate_value = "Bearer"
|
|
231
|
+
credentials_exception = HTTPException(
|
|
232
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
233
|
+
detail="Could not validate credentials",
|
|
234
|
+
headers={"WWW-Authenticate": authenticate_value},
|
|
235
|
+
)
|
|
236
|
+
try:
|
|
237
|
+
payload = jwt.decode(token, key=SECRET_KEY, algorithms=[ALGORITHM])
|
|
238
|
+
client_id = payload.get("sub")
|
|
239
|
+
if client_id is None:
|
|
240
|
+
raise credentials_exception
|
|
241
|
+
token_scopes = payload.get("scopes", [])
|
|
242
|
+
token_groups = payload.get("authorized_groups", [])
|
|
243
|
+
rate_limit = payload.get("rate_limit", DEFAULT_RATE_LIMIT)
|
|
244
|
+
rate_window = payload.get("rate_window", DEFAULT_RATE_WINDOW)
|
|
245
|
+
|
|
246
|
+
token_data = TokenData(
|
|
247
|
+
client_id=client_id,
|
|
248
|
+
scopes=token_scopes,
|
|
249
|
+
authorized_groups=token_groups,
|
|
250
|
+
rate_limit=rate_limit,
|
|
251
|
+
rate_window=rate_window,
|
|
252
|
+
)
|
|
253
|
+
except (InvalidTokenError, ValidationError):
|
|
254
|
+
raise credentials_exception
|
|
255
|
+
|
|
256
|
+
# Check if client still exists
|
|
257
|
+
if client_id not in load_client_registry():
|
|
258
|
+
raise HTTPException(
|
|
259
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
260
|
+
detail="Client no longer exists",
|
|
261
|
+
headers={"WWW-Authenticate": authenticate_value},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Check if client has necessary scopes
|
|
265
|
+
for scope in security_scopes.scopes:
|
|
266
|
+
if scope not in token_data.scopes:
|
|
267
|
+
logger.error(f"{scope} not in {token_data.scopes}, not enough permissions")
|
|
268
|
+
raise HTTPException(
|
|
269
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
270
|
+
detail=f"Not enough permissions. Required scope: {scope}",
|
|
271
|
+
headers={"WWW-Authenticate": authenticate_value},
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Check rate limit
|
|
275
|
+
await check_rate_limit(request, token_data)
|
|
276
|
+
|
|
277
|
+
return token_data
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_token(
|
|
281
|
+
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
|
282
|
+
) -> Token:
|
|
283
|
+
"""Implement the token endpoint for token generation"""
|
|
284
|
+
client_id = (
|
|
285
|
+
form_data.username
|
|
286
|
+
) # In client credentials, username field holds the client_id
|
|
287
|
+
client_secret = form_data.password # Password field holds the client_secret
|
|
288
|
+
|
|
289
|
+
# Load client registry
|
|
290
|
+
client_registry = load_client_registry()
|
|
291
|
+
|
|
292
|
+
# Validate client
|
|
293
|
+
if client_id not in client_registry:
|
|
294
|
+
raise HTTPException(
|
|
295
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
296
|
+
detail="Invalid client credentials",
|
|
297
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
client = client_registry[client_id]
|
|
301
|
+
if client["client_secret"] != client_secret:
|
|
302
|
+
raise HTTPException(
|
|
303
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
304
|
+
detail="Invalid client credentials",
|
|
305
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Validate scopes
|
|
309
|
+
client_scopes = client["scopes"]
|
|
310
|
+
requested_scopes = form_data.scopes
|
|
311
|
+
|
|
312
|
+
# If no scopes requested, grant all available to client
|
|
313
|
+
if not requested_scopes:
|
|
314
|
+
scopes = client_scopes
|
|
315
|
+
else:
|
|
316
|
+
# Check if requested scopes are valid
|
|
317
|
+
for scope in requested_scopes:
|
|
318
|
+
if scope not in client_scopes:
|
|
319
|
+
raise HTTPException(
|
|
320
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
321
|
+
detail=f"Client '{client_id}' does not have access to scope '{scope}'",
|
|
322
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
323
|
+
)
|
|
324
|
+
scopes = requested_scopes
|
|
325
|
+
|
|
326
|
+
# Get authorized groups and rate limit
|
|
327
|
+
authorized_groups = client.get("authorized_groups", [])
|
|
328
|
+
rate_limit = client.get("rate_limit", DEFAULT_RATE_LIMIT)
|
|
329
|
+
rate_window = client.get("rate_window", DEFAULT_RATE_WINDOW)
|
|
330
|
+
|
|
331
|
+
# Create access token
|
|
332
|
+
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
333
|
+
access_token = create_access_token(
|
|
334
|
+
data={
|
|
335
|
+
"sub": client_id,
|
|
336
|
+
"scopes": scopes,
|
|
337
|
+
"authorized_groups": authorized_groups,
|
|
338
|
+
"rate_limit": rate_limit,
|
|
339
|
+
"rate_window": rate_window,
|
|
340
|
+
},
|
|
341
|
+
expires_delta=access_token_expires,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
expires_in = (
|
|
345
|
+
access_token_expires.total_seconds()
|
|
346
|
+
) # Calculate the expiration time in seconds
|
|
347
|
+
|
|
348
|
+
logger.debug(f"Token created for client '{client_id}'")
|
|
349
|
+
return Token(access_token=access_token, token_type="bearer", expires_in=expires_in)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def is_authorized_for_group(token_data: TokenData, group: str) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
Check if a client is authorized for a specific group
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
token_data: The TokenData object containing client information
|
|
358
|
+
group: The group to check authorization for
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
bool: True if authorized, False otherwise
|
|
362
|
+
"""
|
|
363
|
+
# Empty authorized_groups means access to all groups
|
|
364
|
+
if not token_data.authorized_groups:
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
# Otherwise, check if the requested group is in the authorized groups
|
|
368
|
+
return group in token_data.authorized_groups
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
2
|
+
# See AUTHORS.txt
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
# This file is part of BERTrend.
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import uvicorn
|
|
8
|
+
|
|
9
|
+
from bertrend.services.embedding_server.config.settings import get_config
|
|
10
|
+
|
|
11
|
+
# Load the configuration
|
|
12
|
+
CONFIG = get_config()
|
|
13
|
+
|
|
14
|
+
# Set the CUDA_VISIBLE_DEVICES environment variable
|
|
15
|
+
os.environ["CUDA_VISIBLE_DEVICES"] = CONFIG.cuda_visible_devices
|
|
16
|
+
|
|
17
|
+
# Start the FastAPI application
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
uvicorn.run(
|
|
20
|
+
"wattelse.api.embedding.main:app",
|
|
21
|
+
host=CONFIG.host,
|
|
22
|
+
port=CONFIG.port,
|
|
23
|
+
workers=CONFIG.number_workers,
|
|
24
|
+
ssl_keyfile="../key.pem",
|
|
25
|
+
ssl_certfile="../cert.pem",
|
|
26
|
+
)
|