sunholo 0.119.18__tar.gz → 0.120.1__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.
- {sunholo-0.119.18/src/sunholo.egg-info → sunholo-0.120.1}/PKG-INFO +5 -3
- {sunholo-0.119.18 → sunholo-0.120.1}/pyproject.toml +5 -3
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/auth/refresh.py +3 -2
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/file_handling.py +130 -65
- {sunholo-0.119.18 → sunholo-0.120.1/src/sunholo.egg-info}/PKG-INFO +5 -3
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo.egg-info/requires.txt +4 -2
- {sunholo-0.119.18 → sunholo-0.120.1}/LICENSE.txt +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/MANIFEST.in +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/README.md +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/setup.cfg +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/flask/qna_routes.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/langserve.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/route.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/agents/swagger.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/archive/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/archive/archive.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/auth/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/auth/run.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/azure/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/azure/auth.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/azure/blobs.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/bots/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/bots/discord.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/bots/webapp.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/azure.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/images.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/publish.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/cli.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/configs.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/deploy.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/embedder.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/swagger.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/cli/vertex.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/components/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/components/llm.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/components/retriever.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/custom_logging.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/alloydb.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/database.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/lancedb.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/database/uuid.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/cli.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/embedder/embed_metadata.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/excel/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/excel/plugin.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/extract_and_sign.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/genaiv2.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/images.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/init.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/process_funcs_cls.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/genai/safety.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/invoke/async_class.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/langchain_types.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/mcp/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/mcp/cli.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/qna/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/qna/parsers.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/qna/retry.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/senses/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/senses/stream_voice.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/agent_service.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/app.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/my_log.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/tools/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/tools/your_agent.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/agent/vac_service.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/project/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/project/app.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/project/my_log.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/project/vac_service.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/system_services/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/system_services/app.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/templates/system_services/my_log.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/tools/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/api_key.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/big_context.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/config.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/config_class.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/gcp.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/mime.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/parsers.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/utils/version.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/init.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/safety.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_async.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_async_genai2.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_chat_history.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_config.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_genai2.py +0 -0
- {sunholo-0.119.18 → sunholo-0.120.1}/tests/test_unstructured.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: sunholo
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.120.1
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
5
5
|
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
6
|
License: Apache License, Version 2.0
|
@@ -28,6 +28,7 @@ Provides-Extra: test
|
|
28
28
|
Requires-Dist: pytest; extra == "test"
|
29
29
|
Requires-Dist: pytest-cov; extra == "test"
|
30
30
|
Provides-Extra: all
|
31
|
+
Requires-Dist: aiofiles; extra == "all"
|
31
32
|
Requires-Dist: aiohttp; extra == "all"
|
32
33
|
Requires-Dist: anthropic[vertex]; extra == "all"
|
33
34
|
Requires-Dist: asyncpg; extra == "all"
|
@@ -50,7 +51,7 @@ Requires-Dist: google-cloud-pubsub; extra == "all"
|
|
50
51
|
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
|
51
52
|
Requires-Dist: google-cloud-texttospeech; extra == "all"
|
52
53
|
Requires-Dist: google-generativeai>=0.7.1; extra == "all"
|
53
|
-
Requires-Dist: google-genai; extra == "all"
|
54
|
+
Requires-Dist: google-genai>=0.2.2; extra == "all"
|
54
55
|
Requires-Dist: gunicorn; extra == "all"
|
55
56
|
Requires-Dist: httpcore; extra == "all"
|
56
57
|
Requires-Dist: httpx; extra == "all"
|
@@ -122,6 +123,7 @@ Requires-Dist: pytesseract; extra == "pipeline"
|
|
122
123
|
Requires-Dist: tabulate; extra == "pipeline"
|
123
124
|
Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
|
124
125
|
Provides-Extra: gcp
|
126
|
+
Requires-Dist: aiofiles; extra == "gcp"
|
125
127
|
Requires-Dist: anthropic[vertex]; extra == "gcp"
|
126
128
|
Requires-Dist: google-api-python-client; extra == "gcp"
|
127
129
|
Requires-Dist: google-auth-httplib2; extra == "gcp"
|
@@ -136,7 +138,7 @@ Requires-Dist: google-cloud-logging; extra == "gcp"
|
|
136
138
|
Requires-Dist: google-cloud-pubsub; extra == "gcp"
|
137
139
|
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
|
138
140
|
Requires-Dist: google-cloud-texttospeech; extra == "gcp"
|
139
|
-
Requires-Dist: google-genai; extra == "gcp"
|
141
|
+
Requires-Dist: google-genai>=0.2.2; extra == "gcp"
|
140
142
|
Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
|
141
143
|
Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
|
142
144
|
Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "sunholo"
|
7
|
-
version = "0.
|
7
|
+
version = "0.120.1"
|
8
8
|
description = "Large Language Model DevOps - a package to help deploy LLMs to the Cloud."
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.10"
|
@@ -59,6 +59,7 @@ test = [
|
|
59
59
|
# any other test dependencies
|
60
60
|
]
|
61
61
|
all = [
|
62
|
+
"aiofiles",
|
62
63
|
"aiohttp",
|
63
64
|
"anthropic[vertex]",
|
64
65
|
"asyncpg",
|
@@ -81,7 +82,7 @@ all = [
|
|
81
82
|
"google-cloud-discoveryengine>=0.13.4",
|
82
83
|
"google-cloud-texttospeech",
|
83
84
|
"google-generativeai>=0.7.1",
|
84
|
-
"google-genai",
|
85
|
+
"google-genai>=0.2.2",
|
85
86
|
"gunicorn",
|
86
87
|
"httpcore",
|
87
88
|
"httpx",
|
@@ -159,6 +160,7 @@ pipeline = [
|
|
159
160
|
"unstructured[local-inference,all-docs]"
|
160
161
|
]
|
161
162
|
gcp = [
|
163
|
+
"aiofiles",
|
162
164
|
"anthropic[vertex]",
|
163
165
|
"google-api-python-client",
|
164
166
|
"google-auth-httplib2",
|
@@ -173,7 +175,7 @@ gcp = [
|
|
173
175
|
"google-cloud-pubsub",
|
174
176
|
"google-cloud-discoveryengine>=0.13.4",
|
175
177
|
"google-cloud-texttospeech",
|
176
|
-
"google-genai",
|
178
|
+
"google-genai>=0.2.2",
|
177
179
|
"google-generativeai>=0.8.3",
|
178
180
|
"langchain-google-genai>=2.0.0",
|
179
181
|
"langchain_google_alloydb_pg>=0.2.2",
|
@@ -9,8 +9,9 @@ from ..custom_logging import log
|
|
9
9
|
def get_default_email():
|
10
10
|
|
11
11
|
# https://stackoverflow.com/questions/64234214/how-to-generate-a-blob-signed-url-in-google-cloud-run
|
12
|
-
|
13
|
-
gcs_credentials
|
12
|
+
gcs_credentials, project_id = get_default_creds()
|
13
|
+
if gcs_credentials is None:
|
14
|
+
gcs_credentials, project_id = refresh_credentials()
|
14
15
|
|
15
16
|
if gcs_credentials is None:
|
16
17
|
log.error("Could not refresh the credentials properly.")
|
@@ -3,6 +3,7 @@ from ..gcs import get_bytes_from_gcs
|
|
3
3
|
|
4
4
|
from functools import partial
|
5
5
|
import mimetypes
|
6
|
+
import uuid
|
6
7
|
import asyncio
|
7
8
|
import tempfile
|
8
9
|
import re
|
@@ -11,6 +12,7 @@ import traceback
|
|
11
12
|
try:
|
12
13
|
import google.generativeai as genai
|
13
14
|
from google import genai as genaiv2
|
15
|
+
|
14
16
|
except ImportError:
|
15
17
|
genai = None
|
16
18
|
genaiv2 = None
|
@@ -77,32 +79,44 @@ ALLOWED_MIME_TYPES = set(AUDIO_MIMES + VIDEO_MIMES + IMAGE_MIMES + DOCUMENT_MIME
|
|
77
79
|
# ]
|
78
80
|
|
79
81
|
def sanitize_file(filename):
|
82
|
+
"""
|
83
|
+
Sanitize filename to conform to Gemini API requirements:
|
84
|
+
- Only lowercase alphanumeric characters and dashes
|
85
|
+
- Cannot begin or end with a dash
|
86
|
+
"""
|
80
87
|
# Split the filename into name and extension
|
81
88
|
name, extension = os.path.splitext(filename)
|
82
89
|
|
83
|
-
#
|
84
|
-
|
85
|
-
|
90
|
+
# Convert to lowercase
|
91
|
+
name = name.lower()
|
92
|
+
|
93
|
+
# Replace any non-alphanumeric characters (including underscores) with dashes
|
94
|
+
sanitized_name = re.sub(r'[^a-z0-9]', '-', name)
|
95
|
+
|
96
|
+
# Remove consecutive dashes
|
97
|
+
sanitized_name = re.sub(r'-+', '-', sanitized_name)
|
86
98
|
|
87
|
-
#
|
99
|
+
# Remove leading or trailing dashes
|
100
|
+
sanitized_name = sanitized_name.strip('-')
|
101
|
+
|
102
|
+
# If the name is empty after sanitization, use a default
|
103
|
+
if not sanitized_name:
|
104
|
+
sanitized_name = 'file'
|
105
|
+
|
106
|
+
# Limit the length
|
88
107
|
return sanitized_name[:40]
|
89
108
|
|
90
|
-
async def construct_file_content(gs_list, bucket:str, genai_lib=False):
|
109
|
+
async def construct_file_content(gs_list, bucket:str, genai_lib=False, timeout=60):
|
91
110
|
"""
|
92
111
|
Args:
|
93
112
|
- gs_list: a list of dicts representing files in a bucket
|
94
|
-
- contentType: The content type of the file on GCS
|
95
|
-
- storagePath: The path in the bucket
|
96
|
-
- name: The name of the file
|
97
|
-
- url: The URL of the file that can be used to display the contents
|
98
113
|
- bucket: The bucket the files are in
|
99
|
-
-
|
100
|
-
|
114
|
+
- genai_lib: whether its using the genai SDK
|
115
|
+
- timeout: timeout in seconds for the gather operation
|
101
116
|
"""
|
102
117
|
|
103
118
|
file_list = []
|
104
119
|
for element in gs_list:
|
105
|
-
|
106
120
|
the_mime_type = element.get('contentType')
|
107
121
|
if the_mime_type is None:
|
108
122
|
continue
|
@@ -114,7 +128,7 @@ async def construct_file_content(gs_list, bucket:str, genai_lib=False):
|
|
114
128
|
log.warning(f'{the_mime_type} is not in allowed MIME types for {element.get("name")}')
|
115
129
|
|
116
130
|
if not file_list:
|
117
|
-
return {"role": "user", "parts": [{"text": "No eligible contentTypes were found"}]}
|
131
|
+
return [{"role": "user", "parts": [{"text": "No eligible contentTypes were found"}]}]
|
118
132
|
|
119
133
|
content = []
|
120
134
|
|
@@ -124,34 +138,59 @@ async def construct_file_content(gs_list, bucket:str, genai_lib=False):
|
|
124
138
|
img_url = f"gs://{bucket}/{file_info['storagePath']}"
|
125
139
|
display_url = file_info.get('url')
|
126
140
|
mime_type = file_info['contentType']
|
127
|
-
name
|
141
|
+
# Generate a unique name for each file
|
142
|
+
import uuid
|
143
|
+
original_name = sanitize_file(file_info['name'])
|
144
|
+
unique_id = str(uuid.uuid4())[:8]
|
145
|
+
unique_name = sanitize_file(f"{original_name}-{unique_id}")
|
146
|
+
|
128
147
|
display_name = file_info['name']
|
129
|
-
log.info(f"Processing {
|
148
|
+
log.info(f"Processing {unique_name=} {display_name=}")
|
149
|
+
|
130
150
|
try:
|
131
151
|
if not genai_lib:
|
132
|
-
myfile = genai.get_file(
|
152
|
+
myfile = genai.get_file(unique_name)
|
133
153
|
else:
|
134
154
|
client = genaiv2.Client()
|
135
|
-
myfile = client.files.get(name=
|
155
|
+
myfile = client.files.get(name=unique_name)
|
136
156
|
content.append(myfile)
|
137
157
|
content.append(f"You have been given the ability to work with file {display_name=} with {mime_type=} {display_url=}")
|
138
|
-
log.info(f"Found existing genai.get_file {
|
158
|
+
log.info(f"Found existing genai.get_file {unique_name=}")
|
139
159
|
except Exception as e:
|
140
|
-
log.info(f"Not found checking genai.get_file: '{
|
160
|
+
log.info(f"Not found checking genai.get_file: '{unique_name}' {str(e)}")
|
141
161
|
tasks.append(
|
142
162
|
download_gcs_upload_genai(img_url,
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
163
|
+
mime_type=mime_type,
|
164
|
+
name=unique_name,
|
165
|
+
display_url=display_url,
|
166
|
+
display_name=display_name,
|
167
|
+
genai_lib=genai_lib)
|
168
|
+
)
|
149
169
|
|
150
|
-
#
|
170
|
+
# Process files with timeout
|
151
171
|
if tasks:
|
152
|
-
|
153
|
-
|
154
|
-
|
172
|
+
try:
|
173
|
+
# Add timeout to prevent hanging
|
174
|
+
task_results = await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=timeout)
|
175
|
+
|
176
|
+
# Process results, handle any exceptions
|
177
|
+
for result in task_results:
|
178
|
+
if isinstance(result, Exception):
|
179
|
+
log.error(f"Task failed with error: {str(result)}")
|
180
|
+
# Add error message to content
|
181
|
+
content.append({"role": "user", "parts": [{"text": f"Error processing file: {str(result)}"}]})
|
182
|
+
else:
|
183
|
+
# Normalize the result structure based on genai_lib
|
184
|
+
if genai_lib and isinstance(result, list):
|
185
|
+
content.append(result[0]) # The file object
|
186
|
+
content.append({"role": "user", "parts": [{"text": result[1]}]}) # The message
|
187
|
+
else:
|
188
|
+
content.append(result)
|
189
|
+
except asyncio.TimeoutError:
|
190
|
+
log.error(f"Timeout occurred after {timeout} seconds while processing files")
|
191
|
+
content.append({"role": "user", "parts": [{"text": f"Some files could not be processed within the time limit ({timeout} seconds)"}]})
|
192
|
+
|
193
|
+
log.info(f"construct_file_content completed with {len(content)} items")
|
155
194
|
return content
|
156
195
|
|
157
196
|
# Helper function to handle each file download with error handling
|
@@ -169,10 +208,9 @@ async def download_gcs_upload_genai(img_url,
|
|
169
208
|
display_url=None,
|
170
209
|
display_name=None,
|
171
210
|
retries=3, delay=2, genai_lib=False):
|
172
|
-
import aiofiles
|
173
|
-
from google.generativeai.types import file_types
|
174
211
|
"""
|
175
212
|
Downloads and uploads a file with retries in case of failure.
|
213
|
+
Thread-safe implementation using unique file paths.
|
176
214
|
|
177
215
|
Args:
|
178
216
|
- img_url: str The URL of the file to download.
|
@@ -184,9 +222,10 @@ async def download_gcs_upload_genai(img_url,
|
|
184
222
|
Returns:
|
185
223
|
- downloaded_content: The result of the file upload if successful.
|
186
224
|
"""
|
225
|
+
import aiofiles
|
187
226
|
for attempt in range(retries):
|
188
227
|
try:
|
189
|
-
log.info(f"Upload {attempt} for {img_url=}")
|
228
|
+
log.info(f"Upload attempt [{attempt}] for {img_url=}")
|
190
229
|
# Download the file bytes asynchronously
|
191
230
|
file_bytes = await asyncio.to_thread(get_bytes_from_gcs, img_url)
|
192
231
|
if not file_bytes:
|
@@ -200,50 +239,72 @@ async def download_gcs_upload_genai(img_url,
|
|
200
239
|
|
201
240
|
if file_size > 19434343:
|
202
241
|
log.warning(f"File size for {img_url}: {file_size} is too big.")
|
203
|
-
msg =
|
242
|
+
msg = f"The file for {img_url} is too large ({file_size} bytes) to be used directly. Use RAG instead or {display_url=}"
|
204
243
|
return {"role": "user", "parts": [{"text": msg}]}
|
205
244
|
|
206
245
|
extension = mimetypes.guess_extension(mime_type)
|
207
246
|
|
208
|
-
#
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
247
|
+
# Create a unique directory for this upload task
|
248
|
+
unique_id = str(uuid.uuid4())
|
249
|
+
temp_dir = os.path.join(tempfile.gettempdir(), f"upload_{unique_id}")
|
250
|
+
os.makedirs(temp_dir, exist_ok=True)
|
251
|
+
|
252
|
+
# Create a file with unique path
|
253
|
+
file_path = os.path.join(temp_dir, f"file_{unique_id}{extension}")
|
254
|
+
|
255
|
+
log.info(f"Writing file {file_path}")
|
256
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
216
257
|
await f.write(file_bytes)
|
217
258
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
downloaded_content: file_types.File = await asyncio.to_thread(
|
259
|
+
try:
|
260
|
+
if not genai_lib:
|
261
|
+
downloaded_content = await asyncio.to_thread(
|
222
262
|
partial(genai.upload_file, name=name, mime_type=mime_type, display_name=display_name),
|
223
|
-
|
224
|
-
|
263
|
+
file_path
|
264
|
+
)
|
265
|
+
|
266
|
+
# Clean up after successful upload
|
267
|
+
try:
|
268
|
+
os.remove(file_path)
|
269
|
+
os.rmdir(temp_dir)
|
270
|
+
except OSError as e:
|
271
|
+
log.warning(f"Cleanup error (non-critical): {str(e)}")
|
272
|
+
|
225
273
|
return {"role": "user", "parts": [{"file_data": downloaded_content},
|
226
|
-
{"text": f"You have been given the ability to read and work with filename '{display_name
|
227
|
-
|
228
|
-
|
229
|
-
msg = f"Could not upload {sanitized_file} to genai.upload_file: {str(err)} {traceback.format_exc()} {display_url=}"
|
230
|
-
log.error(msg)
|
231
|
-
return {"role": "user", "parts": [{"text": msg}]}
|
232
|
-
else:
|
233
|
-
try:
|
274
|
+
{"text": f"You have been given the ability to read and work with filename '{display_name}' with {mime_type=} {display_url=}"}
|
275
|
+
]}
|
276
|
+
else:
|
234
277
|
client = genaiv2.Client()
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
278
|
+
|
279
|
+
# Use semaphore to limit concurrent uploads
|
280
|
+
async with upload_semaphore:
|
281
|
+
downloaded_content = await asyncio.to_thread(
|
282
|
+
client.files.upload,
|
283
|
+
file=file_path,
|
284
|
+
config=dict(mime_type=mime_type, display_name=display_name)
|
285
|
+
)
|
286
|
+
|
287
|
+
# Clean up after successful upload
|
288
|
+
try:
|
289
|
+
os.remove(file_path)
|
290
|
+
os.rmdir(temp_dir)
|
291
|
+
except OSError as e:
|
292
|
+
log.warning(f"Cleanup error (non-critical): {str(e)}")
|
293
|
+
|
240
294
|
return [downloaded_content,
|
241
|
-
f"You have been given the ability to read and work with filename '{display_name
|
295
|
+
f"You have been given the ability to read and work with filename '{display_name}' with {mime_type=} {display_url=}"]
|
242
296
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
297
|
+
except Exception as err:
|
298
|
+
# Clean up on error
|
299
|
+
try:
|
300
|
+
os.remove(file_path)
|
301
|
+
os.rmdir(temp_dir)
|
302
|
+
except OSError:
|
303
|
+
pass
|
304
|
+
|
305
|
+
msg = f"Could not upload {file_path} to {'genai.upload_file' if not genai_lib else 'genaiv2.client.files.upload'}: {str(err)} {traceback.format_exc()} {display_url=}"
|
306
|
+
log.error(msg)
|
307
|
+
return {"role": "user", "parts": [{"text": msg}]}
|
247
308
|
|
248
309
|
except Exception as err:
|
249
310
|
log.error(f"Error processing file {img_url} {mime_type=} on attempt {attempt + 1}/{retries}: {str(err)}")
|
@@ -255,3 +316,7 @@ async def download_gcs_upload_genai(img_url,
|
|
255
316
|
else:
|
256
317
|
raise err # Raise the error after max retries
|
257
318
|
|
319
|
+
# Add this at the module level
|
320
|
+
# Create a semaphore to limit concurrent uploads
|
321
|
+
upload_semaphore = asyncio.Semaphore(5) # Adjust the value based on your needs
|
322
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: sunholo
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.120.1
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
5
5
|
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
6
|
License: Apache License, Version 2.0
|
@@ -28,6 +28,7 @@ Provides-Extra: test
|
|
28
28
|
Requires-Dist: pytest; extra == "test"
|
29
29
|
Requires-Dist: pytest-cov; extra == "test"
|
30
30
|
Provides-Extra: all
|
31
|
+
Requires-Dist: aiofiles; extra == "all"
|
31
32
|
Requires-Dist: aiohttp; extra == "all"
|
32
33
|
Requires-Dist: anthropic[vertex]; extra == "all"
|
33
34
|
Requires-Dist: asyncpg; extra == "all"
|
@@ -50,7 +51,7 @@ Requires-Dist: google-cloud-pubsub; extra == "all"
|
|
50
51
|
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
|
51
52
|
Requires-Dist: google-cloud-texttospeech; extra == "all"
|
52
53
|
Requires-Dist: google-generativeai>=0.7.1; extra == "all"
|
53
|
-
Requires-Dist: google-genai; extra == "all"
|
54
|
+
Requires-Dist: google-genai>=0.2.2; extra == "all"
|
54
55
|
Requires-Dist: gunicorn; extra == "all"
|
55
56
|
Requires-Dist: httpcore; extra == "all"
|
56
57
|
Requires-Dist: httpx; extra == "all"
|
@@ -122,6 +123,7 @@ Requires-Dist: pytesseract; extra == "pipeline"
|
|
122
123
|
Requires-Dist: tabulate; extra == "pipeline"
|
123
124
|
Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
|
124
125
|
Provides-Extra: gcp
|
126
|
+
Requires-Dist: aiofiles; extra == "gcp"
|
125
127
|
Requires-Dist: anthropic[vertex]; extra == "gcp"
|
126
128
|
Requires-Dist: google-api-python-client; extra == "gcp"
|
127
129
|
Requires-Dist: google-auth-httplib2; extra == "gcp"
|
@@ -136,7 +138,7 @@ Requires-Dist: google-cloud-logging; extra == "gcp"
|
|
136
138
|
Requires-Dist: google-cloud-pubsub; extra == "gcp"
|
137
139
|
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
|
138
140
|
Requires-Dist: google-cloud-texttospeech; extra == "gcp"
|
139
|
-
Requires-Dist: google-genai; extra == "gcp"
|
141
|
+
Requires-Dist: google-genai>=0.2.2; extra == "gcp"
|
140
142
|
Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
|
141
143
|
Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
|
142
144
|
Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
|
@@ -6,6 +6,7 @@ ruamel.yaml
|
|
6
6
|
tenacity
|
7
7
|
|
8
8
|
[all]
|
9
|
+
aiofiles
|
9
10
|
aiohttp
|
10
11
|
anthropic[vertex]
|
11
12
|
asyncpg
|
@@ -28,7 +29,7 @@ google-cloud-pubsub
|
|
28
29
|
google-cloud-discoveryengine>=0.13.4
|
29
30
|
google-cloud-texttospeech
|
30
31
|
google-generativeai>=0.7.1
|
31
|
-
google-genai
|
32
|
+
google-genai>=0.2.2
|
32
33
|
gunicorn
|
33
34
|
httpcore
|
34
35
|
httpx
|
@@ -97,6 +98,7 @@ requests
|
|
97
98
|
rich
|
98
99
|
|
99
100
|
[gcp]
|
101
|
+
aiofiles
|
100
102
|
anthropic[vertex]
|
101
103
|
google-api-python-client
|
102
104
|
google-auth-httplib2
|
@@ -111,7 +113,7 @@ google-cloud-logging
|
|
111
113
|
google-cloud-pubsub
|
112
114
|
google-cloud-discoveryengine>=0.13.4
|
113
115
|
google-cloud-texttospeech
|
114
|
-
google-genai
|
116
|
+
google-genai>=0.2.2
|
115
117
|
google-generativeai>=0.8.3
|
116
118
|
langchain-google-genai>=2.0.0
|
117
119
|
langchain_google_alloydb_pg>=0.2.2
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{sunholo-0.119.18 → sunholo-0.120.1}/src/sunholo/discovery_engine/discovery_engine_client.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|