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.
Files changed (201) hide show
  1. {bertrend-0.3.4 → bertrend-0.3.5}/PKG-INFO +5 -4
  2. {bertrend-0.3.4 → bertrend-0.3.5}/README.md +1 -1
  3. bertrend-0.3.5/bertrend/services/embedding_server/README.md +31 -0
  4. bertrend-0.3.5/bertrend/services/embedding_server/config/default_config.toml +7 -0
  5. bertrend-0.3.5/bertrend/services/embedding_server/config/settings.py +34 -0
  6. bertrend-0.3.5/bertrend/services/embedding_server/main.py +14 -0
  7. bertrend-0.3.5/bertrend/services/embedding_server/models.py +12 -0
  8. bertrend-0.3.5/bertrend/services/embedding_server/routers/authentication.py +45 -0
  9. bertrend-0.3.5/bertrend/services/embedding_server/routers/embeddings.py +39 -0
  10. bertrend-0.3.5/bertrend/services/embedding_server/routers/info.py +29 -0
  11. bertrend-0.3.5/bertrend/services/embedding_server/security.py +368 -0
  12. bertrend-0.3.5/bertrend/services/embedding_server/start.py +26 -0
  13. bertrend-0.3.5/bertrend/services/embedding_server/status.py +30 -0
  14. bertrend-0.3.5/bertrend/services/embedding_server/stop.py +14 -0
  15. bertrend-0.3.5/bertrend/tests/test_installation.py +103 -0
  16. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/data_structure.py +1 -1
  17. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/prompts.py +84 -7
  18. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/weak_signals.py +1 -1
  19. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/data_loading.py +4 -0
  20. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/common/crontab_utils.py +28 -3
  21. bertrend-0.3.5/bertrend_apps/exploration/curebot/__init__.py +4 -0
  22. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/app_utils.py +11 -3
  23. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/start_newsletter_generator.sh +1 -1
  24. bertrend-0.3.5/bertrend_apps/newsletters/__init__.py +4 -0
  25. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/app.py +2 -2
  26. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_config.py +60 -26
  27. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_data.py +17 -11
  28. bertrend-0.3.5/bertrend_apps/prospective_demo/i18n_translations.py +380 -0
  29. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/models_info.py +77 -54
  30. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_generation.py +25 -24
  31. {bertrend-0.3.4 → bertrend-0.3.5}/pyproject.toml +4 -3
  32. bertrend-0.3.4/bertrend/tests/test_installation.py +0 -181
  33. bertrend-0.3.4/bertrend_apps/exploration/curebot/old_veille_analyse.py +0 -298
  34. bertrend-0.3.4/bertrend_apps/prospective_demo/i18n_translations.py +0 -91
  35. {bertrend-0.3.4 → bertrend-0.3.5}/AUTHORS.txt +0 -0
  36. {bertrend-0.3.4 → bertrend-0.3.5}/LICENSE.md +0 -0
  37. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/BERTopicModel.py +0 -0
  38. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/BERTrend.py +0 -0
  39. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/__init__.py +0 -0
  40. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/bertopic_default_config.toml +0 -0
  41. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/bertrend_default_config.toml +0 -0
  42. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/parameters.py +0 -0
  43. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/config/services_default_config.toml +0 -0
  44. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/__init__.py +0 -0
  45. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/__init__.py +0 -0
  46. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/data_loading_component.py +0 -0
  47. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/embed_documents_component.py +0 -0
  48. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/i18n.py +0 -0
  49. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/i18n_translations.py +0 -0
  50. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/icons.py +0 -0
  51. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/parameters_component.py +0 -0
  52. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/demos_utils/state_utils.py +0 -0
  53. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_summarizer_app.sh +0 -0
  54. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_topic_analysis_app.sh +0 -0
  55. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/start_weak_signals_app.sh +0 -0
  56. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/summarization/__init__.py +0 -0
  57. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/summarization/summarizer_app.py +0 -0
  58. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/__init__.py +0 -0
  59. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/app.py +0 -0
  60. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/app_utils.py +0 -0
  61. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/__init__.py +0 -0
  62. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/explore_topics.py +0 -0
  63. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +0 -0
  64. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +0 -0
  65. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +0 -0
  66. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/demo_pages/training_page.py +0 -0
  67. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/topic_analysis/i18n_translations.py +0 -0
  68. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/__init__.py +0 -0
  69. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/app.py +0 -0
  70. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/i18n_translations.py +0 -0
  71. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/demos/weak_signals/visualizations_utils.py +0 -0
  72. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/__init__.py +0 -0
  73. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/newsletter.css +0 -0
  74. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/newsletter_features.py +0 -0
  75. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/openai_client.py +0 -0
  76. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/llm_utils/prompts.py +0 -0
  77. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/__init__.py +0 -0
  78. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/temporal_metrics_embedding.py +0 -0
  79. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/metrics/topic_metrics.py +0 -0
  80. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/common_ngrams.json +0 -0
  81. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-en.json +0 -0
  82. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-fr.json +0 -0
  83. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/resources/stopwords-rte.json +0 -0
  84. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/__init__.py +0 -0
  85. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/authentication.py +0 -0
  86. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/embedding_client.py +0 -0
  87. {bertrend-0.3.4/bertrend/services/summary → bertrend-0.3.5/bertrend/services/embedding_server}/__init__.py +0 -0
  88. {bertrend-0.3.4/bertrend/tests → bertrend-0.3.5/bertrend/services/embedding_server/routers}/__init__.py +0 -0
  89. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/embedding_service.py +0 -0
  90. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summarizer.py +0 -0
  91. {bertrend-0.3.4/bertrend/topic_analysis → bertrend-0.3.5/bertrend/services/summary}/__init__.py +0 -0
  92. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/abstractive_summarizer.py +0 -0
  93. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/chatgpt_summarizer.py +0 -0
  94. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/extractive_summarizer.py +0 -0
  95. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/lexrank.py +0 -0
  96. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/services/summary/prompts.py +0 -0
  97. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/README_integration_tests.md +0 -0
  98. {bertrend-0.3.4/bertrend/trend_analysis → bertrend-0.3.5/bertrend/tests}/__init__.py +0 -0
  99. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/conftest.py +0 -0
  100. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_abstractive_summarizer.py +0 -0
  101. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_bertrend.py +0 -0
  102. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_cache_utils.py +0 -0
  103. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_chatgpt_summarizer.py +0 -0
  104. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_config_utils.py +0 -0
  105. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_data_loading.py +0 -0
  106. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_data_structure.py +0 -0
  107. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_embedding_client.py +0 -0
  108. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_extractive_summarizer.py +0 -0
  109. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_integration.py +0 -0
  110. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_newsletter_features.py +0 -0
  111. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_openai_client.py +0 -0
  112. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_representative_docs.py +0 -0
  113. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_summarizer.py +0 -0
  114. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_temporal_metrics_embedding.py +0 -0
  115. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_description.py +0 -0
  116. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_metrics.py +0 -0
  117. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_topic_model.py +0 -0
  118. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_data_structure.py +0 -0
  119. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_prompts.py +0 -0
  120. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_visualizations.py +0 -0
  121. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_trend_analysis_weak_signals.py +0 -0
  122. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_utils_init.py +0 -0
  123. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/tests/test_visualizations.py +0 -0
  124. {bertrend-0.3.4/bertrend_apps → bertrend-0.3.5/bertrend/topic_analysis}/__init__.py +0 -0
  125. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/data_structure.py +0 -0
  126. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/prompts.py +0 -0
  127. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/representative_docs.py +0 -0
  128. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/topic_description.py +0 -0
  129. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/topic_analysis/visualizations.py +0 -0
  130. {bertrend-0.3.4/bertrend_apps/common → bertrend-0.3.5/bertrend/trend_analysis}/__init__.py +0 -0
  131. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/signal_llm_template_en.html +0 -0
  132. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/signal_llm_template_fr.html +0 -0
  133. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/trend_analysis/visualizations.py +0 -0
  134. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/__init__.py +0 -0
  135. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/cache_utils.py +0 -0
  136. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend/utils/config_utils.py +0 -0
  137. {bertrend-0.3.4/bertrend_apps/exploration → bertrend-0.3.5/bertrend_apps}/__init__.py +0 -0
  138. {bertrend-0.3.4/bertrend_apps/exploration/curebot → bertrend-0.3.5/bertrend_apps/common}/__init__.py +0 -0
  139. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/common/mail_utils.py +0 -0
  140. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/arxiv_feed.toml +0 -0
  141. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/decarbonation_feed.toml +0 -0
  142. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/llm_en_feed.toml +0 -0
  143. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/llm_feed.toml +0 -0
  144. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/nlp_feed.toml +0 -0
  145. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/feeds/rte_feed.toml +0 -0
  146. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/gmail_credentials.json +0 -0
  147. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +0 -0
  148. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +0 -0
  149. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +0 -0
  150. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +0 -0
  151. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +0 -0
  152. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +0 -0
  153. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +0 -0
  154. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +0 -0
  155. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +0 -0
  156. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/__init__.py +0 -0
  157. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/__main__.py +0 -0
  158. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/arxiv_provider.py +0 -0
  159. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/atom_feed_provider.py +0 -0
  160. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/bing_news_provider.py +0 -0
  161. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/data_provider.py +0 -0
  162. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/google_news_provider.py +0 -0
  163. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/newscatcher_provider.py +0 -0
  164. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/data_provider/utils.py +0 -0
  165. {bertrend-0.3.4/bertrend_apps/newsletters → bertrend-0.3.5/bertrend_apps/exploration}/__init__.py +0 -0
  166. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/app.py +0 -0
  167. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/config.toml +0 -0
  168. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/import_extraction_article.ipynb +0 -0
  169. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/prompts.py +0 -0
  170. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab1.py +0 -0
  171. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab2.py +0 -0
  172. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/curebot/tabs/tab3.py +0 -0
  173. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/Main_page.py +0 -0
  174. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/__init__.py +0 -0
  175. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +0 -0
  176. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/__init__.py +0 -0
  177. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/entity_utils.py +0 -0
  178. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/__init__.py +0 -0
  179. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/common_factory.py +0 -0
  180. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +0 -0
  181. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/nlp.config +0 -0
  182. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/exploration/geolocalization/spacy/utils.py +0 -0
  183. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/install_feeds_and_newsletters.py +0 -0
  184. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/newsletters/__main__.py +0 -0
  185. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/.streamlit/config.toml +0 -0
  186. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/.streamlit/secrets.toml +0 -0
  187. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/__init__.py +0 -0
  188. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/authentication.py +0 -0
  189. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_analysis.py +0 -0
  190. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_common.py +0 -0
  191. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/dashboard_signals.py +0 -0
  192. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/feeds_common.py +0 -0
  193. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/llm_utils.py +0 -0
  194. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/perf_utils.py +0 -0
  195. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/process_new_data.py +0 -0
  196. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_template.html +0 -0
  197. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/report_utils.py +0 -0
  198. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/start_demo.sh +0 -0
  199. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/prospective_demo/streamlit_utils.py +0 -0
  200. {bertrend-0.3.4 → bertrend-0.3.5}/bertrend_apps/start_apps.sh +0 -0
  201. {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.4
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.3.3)
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.0)
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: https://github.com/rte-france/wattelse/tree/main/wattelse/api/embedding. (this implementation is compatible with our code)
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
  ![parameters](docs/images/parameters.png)
@@ -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: https://github.com/rte-france/wattelse/tree/main/wattelse/api/embedding. (this implementation is compatible with our code)
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
  ![parameters](docs/images/parameters.png)
@@ -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,7 @@
1
+ host = "0.0.0.0"
2
+ port = 6464
3
+ model_name = "OrdalieTech/Solon-embeddings-large-0.1"
4
+ #model_name=dangvantuan/sentence-camembert-large
5
+ #model_name=antoinelouis/biencoder-camembert-base-mmarcoFR
6
+ number_workers = 2
7
+ cuda_visible_devices = "0"
@@ -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
+ )