abstractcore 2.19.2__tar.gz → 2.20.0__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.
- {abstractcore-2.19.2 → abstractcore-2.20.0}/PKG-INFO +15 -1
- {abstractcore-2.19.2 → abstractcore-2.20.0}/README.md +11 -0
- abstractcore-2.20.0/abstractcore/comms/__init__.py +5 -0
- abstractcore-2.20.0/abstractcore/comms/email/__init__.py +156 -0
- abstractcore-2.20.0/abstractcore/comms/email/bodystructure.py +253 -0
- abstractcore-2.20.0/abstractcore/comms/email/client.py +1310 -0
- abstractcore-2.20.0/abstractcore/comms/email/context.py +106 -0
- abstractcore-2.20.0/abstractcore/comms/email/errors.py +370 -0
- abstractcore-2.20.0/abstractcore/comms/email/imap_codec.py +267 -0
- abstractcore-2.20.0/abstractcore/comms/email/legacy.py +265 -0
- abstractcore-2.20.0/abstractcore/comms/email/limits.py +151 -0
- abstractcore-2.20.0/abstractcore/comms/email/models.py +653 -0
- abstractcore-2.20.0/abstractcore/comms/email/oauth.py +597 -0
- abstractcore-2.20.0/abstractcore/comms/email/policy.py +322 -0
- abstractcore-2.20.0/abstractcore/comms/email/search.py +209 -0
- abstractcore-2.20.0/abstractcore/comms/email/store.py +546 -0
- abstractcore-2.20.0/abstractcore/comms/email/vault.py +323 -0
- abstractcore-2.20.0/abstractcore/config/email_cli.py +571 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/main.py +5 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/manager.py +80 -8
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/console/themes.py +1 -1
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/console/web.py +340 -3
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/app.py +5 -0
- abstractcore-2.20.0/abstractcore/server/email_routes.py +422 -0
- abstractcore-2.20.0/abstractcore/testing/__init__.py +2 -0
- abstractcore-2.20.0/abstractcore/testing/mailserver.py +1131 -0
- abstractcore-2.20.0/abstractcore/tools/comms_tools.py +1056 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/email_digests.py +8 -32
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/inventory.py +20 -3
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/risk_facts.py +4 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/version.py +1 -1
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/PKG-INFO +15 -1
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/SOURCES.txt +19 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/requires.txt +3 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/pyproject.toml +11 -1
- abstractcore-2.20.0/tests/test_email_digests.py +24 -0
- abstractcore-2.19.2/abstractcore/tools/comms_tools.py +0 -1722
- abstractcore-2.19.2/tests/test_email_digests.py +0 -40
- {abstractcore-2.19.2 → abstractcore-2.20.0}/LICENSE +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/__main__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/app_config_utils.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/deepsearch.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/extractor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/intent.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/judge.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/apps/summarizer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/architectures/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/architectures/detection.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/architectures/enums.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/architectures/response_postprocessing.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/architectures/thinking_controls.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/OCRA.ttf +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/OCRB.ttf +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/OCRBL.ttf +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/README.md +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/abstractframework_identity.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/architecture_formats.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/bloc-schema.jsonld +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/model_capabilities.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/model_capabilities.schema.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/model_downloads_catalog.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/model_downloads_catalog.schema.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/semantic_models.md +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/assets/session_schema.json +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/errors.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/host.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/music_selectors.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/registry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/scene3d_selectors.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/types.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/capabilities/vision_catalog.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/analytics.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/cache.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/config.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/exceptions.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/glyph_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/optimizer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/orchestrator.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/pil_text_renderer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/quality.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/text_formatter.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/compression/vision_compressor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/capability_defaults.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/engines.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/host_jobs.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/model_catalog.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/model_materializer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/model_verify.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/models_engines_cli.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/provider_profiles.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/recommendations.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/route_engines.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/config/vision_config.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/console/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/console/theme_sync.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/bloc_kv.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/bloc_metadata.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/cached_session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/degeneration.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/endpoint_damping.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/enums.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/factory.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/file_blocs.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/file_boxes.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/generate_contract.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/interface.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/multimodal_generation.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/output_specs.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/retry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/core/types.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/download.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/embeddings/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/embeddings/manager.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/embeddings/models.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/endpoint/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/endpoint/app.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/events/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/exceptions/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/client.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/factory.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/naming.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/stdio_client.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/mcp/tool_source.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/auto_handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/base.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/capabilities.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/delivery.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/enrichment.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/handlers/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/handlers/anthropic_handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/handlers/local_handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/handlers/openai_handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/openai_parts.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/pdf_routing.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/audio_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/direct_pdf_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/glyph_pdf_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/image_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/office_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/pdf_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/text_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/processors/video_processor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/types.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/utils/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/utils/image_scaler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/utils/video_frames.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/media/vision_fallback.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/basic_deepsearch.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/basic_extractor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/basic_intent.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/basic_judge.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/processing/basic_summarizer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/_http.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/anthropic_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/base.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/endpoint_context.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/generation_cancel.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/generation_progress.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/hf_residency.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/huggingface_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/lmstudio_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_model_rules.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_native_cache.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_native_session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_prefill_observer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_qwen4.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_residency.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_runtime.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/mlx_vision_addon.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/model_capabilities.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/model_config_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/ollama_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/openai_compatible_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/openai_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/openrouter_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/portkey_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/process_residency.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/registry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/speculation.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/streaming.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/tokenizer_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/vllm_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/providers/weights_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/py.typed +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/__main__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/audio_endpoints.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/camera_endpoints.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/capability_generation.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/console_routes.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/credentials.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/first_run.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/host_routes.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/scene3d_endpoints.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/session_routes.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/server/vision_endpoints.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/structured/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/structured/handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/structured/retry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/structured/schema_compat.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/abstractignore.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/arg_canonicalizer.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/arg_coercion.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/browser_tools.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/code_analysis.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/common_tools.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/core.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/fetch_url_ssrf.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/handler.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/parser.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/process_tree.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/registry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/shell_session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/shell_tools.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/syntax_rewriter.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/tag_rewriter.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/telegram_tdlib.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/telegram_tools.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/tools/wire_naming.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/__init__.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/async_stream.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/cli.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/context_calibration.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/context_estimate.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/data_registry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/display.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/file_filters.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/host_profile.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/hostinfo.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/identity.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/install_settings.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/jsonish.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/memory.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/message_preprocessor.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/model_cache.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/model_fit.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/residency.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/self_fixes.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/structured_logging.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/token_utils.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/trace_export.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/truncation.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/vlm_token_calculator.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/windows_dll.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore/utils/workspace_paths.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/dependency_links.txt +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/entry_points.txt +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/abstractcore.egg-info/top_level.txt +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/setup.cfg +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_abstractendpoint_singleton_provider.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_agentic_cli_compatibility.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_basic_session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_cache_dtype.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_endpoint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_engine_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_model_config_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_tokenizer_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_kv_weights_fingerprint.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_bloc_metadata_json_extract.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_cached_session_kv_mode.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_cached_session_kv_thinking_prefix.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_capabilities_registry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_capabilities_registry_preferred_backends_from_config.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_complete_integration.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_comprehensive_events.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_console_web.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_core_components.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_display_rule.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_embedding_model_token_warnings.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_enhanced_prompt.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_environment_variable_tool_call_tags.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_factory.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_file_bloc_store_unit.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_file_boxes_cached_session.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_final_accuracy.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_final_comprehensive.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_final_graceful_errors.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_generate_contract.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_generate_response_reasoning_property_unit.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_generate_with_outputs.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_graceful_fallback.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_import_debug.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_import_safety.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_incremental_thinking_tag_stripper.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_install_hints_no_bare_packages.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_install_settings.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_integrated_functionality.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_llms_sources.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_mcp_integration.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_mcp_stdio_client.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_multimodal_generate_output.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_openai_prompt_cache_key_passthrough.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_output_specs.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_packaging_assets.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_packaging_extras.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_prompt_cache_api.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_prompt_cache_bloc_composition.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_prompt_cache_bloc_e2e_gate.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_prompt_cache_control_plane.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_prompt_cache_prepare_modules_thinking_http.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_retry_observability.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_retry_strategy.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_seed_determinism.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_seed_temperature_basic.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_sensory_prompting.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_server_prompt_cache_control_plane_proxy.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_server_prompt_cache_key_passthrough.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_session_reasoning_persistence_unit.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_structured_output_truncation_retry.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_text_only_model_experience.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_unload_memory.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_user_scenario_validation.py +0 -0
- {abstractcore-2.19.2 → abstractcore-2.20.0}/tests/test_voice_host_credentials.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractcore
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.20.0
|
|
4
4
|
Summary: Unified interface to all LLM providers with essential infrastructure for tool calling, streaming, and model management
|
|
5
5
|
Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
6
6
|
Maintainer-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
@@ -48,6 +48,8 @@ Requires-Dist: fastapi<1.0.0,>=0.100.0
|
|
|
48
48
|
Requires-Dist: uvicorn[standard]<1.0.0,>=0.23.0
|
|
49
49
|
Requires-Dist: python-multipart<1.0.0,>=0.0.6
|
|
50
50
|
Requires-Dist: sse-starlette<2.0.0,>=1.6.0
|
|
51
|
+
Requires-Dist: cryptography>=41.0.0
|
|
52
|
+
Requires-Dist: keyring>=24.0.0
|
|
51
53
|
Requires-Dist: abstractvoice>=0.13.2
|
|
52
54
|
Requires-Dist: abstractvision>=0.3.33
|
|
53
55
|
Requires-Dist: abstractmusic>=0.1.15; python_version >= "3.10"
|
|
@@ -173,6 +175,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
|
173
175
|
Requires-Dist: responses>=0.23.0; extra == "test"
|
|
174
176
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11" and extra == "test"
|
|
175
177
|
Requires-Dist: jinja2<4.0.0,>=3.0.0; extra == "test"
|
|
178
|
+
Requires-Dist: aiosmtpd<2.0.0,>=1.4.4; extra == "test"
|
|
176
179
|
Provides-Extra: docs
|
|
177
180
|
Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
178
181
|
Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
|
|
@@ -452,6 +455,7 @@ You can also persist settings (including API keys) via the config CLI:
|
|
|
452
455
|
- Embeddings and semantic search → [Embeddings](docs/embeddings.md)
|
|
453
456
|
- Observability: global event bus + interaction traces → [Architecture](docs/architecture.md), [API Reference (Events)](docs/api-reference.md#eventtype), [Interaction Tracing](docs/interaction-tracing.md)
|
|
454
457
|
- MCP (Model Context Protocol): discover tools from MCP servers (HTTP/stdio) → [MCP](docs/mcp.md)
|
|
458
|
+
- Email: one IMAP/SMTP account (password or OAuth2), encrypted credentials, recipient policy, send limits, read-only mailbox and email tools → [Email](docs/email.md)
|
|
455
459
|
- OpenAI-compatible server: one `/v1` gateway for chat + optional `/v1/images/*` and `/v1/audio/*` endpoints → [Server](docs/server.md)
|
|
456
460
|
|
|
457
461
|
## Tool calling (passthrough by default)
|
|
@@ -721,6 +725,15 @@ abstractcore engines status --probe # Ollama / LM Studio / MLX / ll
|
|
|
721
725
|
abstractcore engines install ollama --dry-run # the exact install command
|
|
722
726
|
```
|
|
723
727
|
|
|
728
|
+
Email (see [Email](docs/email.md)):
|
|
729
|
+
|
|
730
|
+
```bash
|
|
731
|
+
abstractcore email connect --address me@example.com \
|
|
732
|
+
--imap-host imap.example.com --smtp-host smtp.example.com --password <value>
|
|
733
|
+
abstractcore email test
|
|
734
|
+
abstractcore email status
|
|
735
|
+
```
|
|
736
|
+
|
|
724
737
|
## Built-in CLI apps
|
|
725
738
|
|
|
726
739
|
AbstractCore also ships with ready-to-use CLI apps:
|
|
@@ -759,6 +772,7 @@ Core features:
|
|
|
759
772
|
- [Capabilities](docs/capabilities.md) — supported features and current limitations
|
|
760
773
|
- [Interaction Tracing](docs/interaction-tracing.md) — inspect prompts/responses/usage for observability
|
|
761
774
|
- [MCP](docs/mcp.md) — consume MCP tool servers (HTTP/stdio) as tool sources
|
|
775
|
+
- [Email](docs/email.md) — connect a mailbox, recipient policy, send limits, email tools and the `abstractcore.comms.email` library
|
|
762
776
|
|
|
763
777
|
Reference and internals:
|
|
764
778
|
- [Architecture](docs/architecture.md) — system overview + event system
|
|
@@ -270,6 +270,7 @@ You can also persist settings (including API keys) via the config CLI:
|
|
|
270
270
|
- Embeddings and semantic search → [Embeddings](docs/embeddings.md)
|
|
271
271
|
- Observability: global event bus + interaction traces → [Architecture](docs/architecture.md), [API Reference (Events)](docs/api-reference.md#eventtype), [Interaction Tracing](docs/interaction-tracing.md)
|
|
272
272
|
- MCP (Model Context Protocol): discover tools from MCP servers (HTTP/stdio) → [MCP](docs/mcp.md)
|
|
273
|
+
- Email: one IMAP/SMTP account (password or OAuth2), encrypted credentials, recipient policy, send limits, read-only mailbox and email tools → [Email](docs/email.md)
|
|
273
274
|
- OpenAI-compatible server: one `/v1` gateway for chat + optional `/v1/images/*` and `/v1/audio/*` endpoints → [Server](docs/server.md)
|
|
274
275
|
|
|
275
276
|
## Tool calling (passthrough by default)
|
|
@@ -539,6 +540,15 @@ abstractcore engines status --probe # Ollama / LM Studio / MLX / ll
|
|
|
539
540
|
abstractcore engines install ollama --dry-run # the exact install command
|
|
540
541
|
```
|
|
541
542
|
|
|
543
|
+
Email (see [Email](docs/email.md)):
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
abstractcore email connect --address me@example.com \
|
|
547
|
+
--imap-host imap.example.com --smtp-host smtp.example.com --password <value>
|
|
548
|
+
abstractcore email test
|
|
549
|
+
abstractcore email status
|
|
550
|
+
```
|
|
551
|
+
|
|
542
552
|
## Built-in CLI apps
|
|
543
553
|
|
|
544
554
|
AbstractCore also ships with ready-to-use CLI apps:
|
|
@@ -577,6 +587,7 @@ Core features:
|
|
|
577
587
|
- [Capabilities](docs/capabilities.md) — supported features and current limitations
|
|
578
588
|
- [Interaction Tracing](docs/interaction-tracing.md) — inspect prompts/responses/usage for observability
|
|
579
589
|
- [MCP](docs/mcp.md) — consume MCP tool servers (HTTP/stdio) as tool sources
|
|
590
|
+
- [Email](docs/email.md) — connect a mailbox, recipient policy, send limits, email tools and the `abstractcore.comms.email` library
|
|
580
591
|
|
|
581
592
|
Reference and internals:
|
|
582
593
|
- [Architecture](docs/architecture.md) — system overview + event system
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""`abstractcore.comms.email` — the framework's one mail implementation.
|
|
2
|
+
|
|
3
|
+
from abstractcore.comms.email import (
|
|
4
|
+
EmailAccount, ImapSettings, SmtpSettings, EmailSecret, EmailClient,
|
|
5
|
+
EmailAccountStore, RecipientPolicy, SendLimits, SearchCriteria, OutgoingMessage,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
account = EmailAccount.build(
|
|
9
|
+
address="me@example.test",
|
|
10
|
+
imap=ImapSettings.build("imap.example.test", security="ssl"),
|
|
11
|
+
smtp=SmtpSettings.build("smtp.example.test", security="starttls"),
|
|
12
|
+
)
|
|
13
|
+
store = EmailAccountStore() # the local AbstractCore settings
|
|
14
|
+
store.connect(account, EmailSecret("app-password"))
|
|
15
|
+
ctx = store.context()
|
|
16
|
+
ctx.client().search(SearchCriteria.build(unseen=True, since="7d"), limit=20)
|
|
17
|
+
ctx.send(OutgoingMessage(to=("me@example.test",), subject="Hello", text="Hi"))
|
|
18
|
+
|
|
19
|
+
Modules: `models` (typed settings), `client` (IMAP read-only + SMTP, verified TLS),
|
|
20
|
+
`bodystructure` (the MIME structure used to fetch only the parts needed),
|
|
21
|
+
`policy` (recipient allowlist / denylist), `limits` (send rate limits), `vault` (encrypted
|
|
22
|
+
credentials), `oauth` (OAuth2 / XOAUTH2), `store` (settings + secret + status), `context`
|
|
23
|
+
(the guarded send), `errors` (typed errors), `legacy` (one-time import of pre-2.20 settings).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .client import DEFAULT_MAX_MESSAGE_BYTES, MAX_LIST_LIMIT, EmailClient, tls_context
|
|
27
|
+
from .context import EmailContext, guarded_send
|
|
28
|
+
from .errors import (
|
|
29
|
+
EmailAgentToolsOff,
|
|
30
|
+
EmailAttachmentNotFound,
|
|
31
|
+
EmailAuthFailed,
|
|
32
|
+
EmailDisabled,
|
|
33
|
+
EmailError,
|
|
34
|
+
EmailInvalidMessage,
|
|
35
|
+
EmailInvalidSettings,
|
|
36
|
+
EmailMailboxMissing,
|
|
37
|
+
EmailMessageNotFound,
|
|
38
|
+
EmailMessageTooLarge,
|
|
39
|
+
EmailNotConfigured,
|
|
40
|
+
EmailOAuthFailed,
|
|
41
|
+
EmailOAuthPending,
|
|
42
|
+
EmailOAuthReauthorize,
|
|
43
|
+
EmailPolicyRefused,
|
|
44
|
+
EmailProtocolError,
|
|
45
|
+
EmailQuotaExceeded,
|
|
46
|
+
EmailRateLimited,
|
|
47
|
+
EmailReadOnlyViolation,
|
|
48
|
+
EmailRecipientRefused,
|
|
49
|
+
EmailSecretUnavailable,
|
|
50
|
+
EmailServerError,
|
|
51
|
+
EmailTlsFailed,
|
|
52
|
+
EmailTransient,
|
|
53
|
+
EmailUnreachable,
|
|
54
|
+
)
|
|
55
|
+
from .limits import SendRateLimiter
|
|
56
|
+
from .models import (
|
|
57
|
+
Attachment,
|
|
58
|
+
AttachmentInfo,
|
|
59
|
+
EmailAccount,
|
|
60
|
+
EmailSecret,
|
|
61
|
+
FetchResult,
|
|
62
|
+
ImapSettings,
|
|
63
|
+
MailCursor,
|
|
64
|
+
MessageDetail,
|
|
65
|
+
MessageSummary,
|
|
66
|
+
OAuthSettings,
|
|
67
|
+
OutgoingMessage,
|
|
68
|
+
SendLimits,
|
|
69
|
+
SendResult,
|
|
70
|
+
SmtpSettings,
|
|
71
|
+
)
|
|
72
|
+
from .oauth import (
|
|
73
|
+
BUILTIN_CLIENTS,
|
|
74
|
+
DeviceAuthorization,
|
|
75
|
+
LoopbackAuthorization,
|
|
76
|
+
OAuthTokenClient,
|
|
77
|
+
OAuthTokenProvider,
|
|
78
|
+
TokenSet,
|
|
79
|
+
builtin_client,
|
|
80
|
+
provider_preset,
|
|
81
|
+
resolve_oauth_client,
|
|
82
|
+
xoauth2_string,
|
|
83
|
+
)
|
|
84
|
+
from .policy import PolicyDecision, RecipientPolicy, evaluate, normalize_address, normalize_domain, parse_recipients
|
|
85
|
+
from .search import SearchCriteria
|
|
86
|
+
from .store import EmailAccountStore, EmailSettings
|
|
87
|
+
from .vault import SecretVault
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
"DEFAULT_MAX_MESSAGE_BYTES",
|
|
91
|
+
"MAX_LIST_LIMIT",
|
|
92
|
+
"BUILTIN_CLIENTS",
|
|
93
|
+
"Attachment",
|
|
94
|
+
"AttachmentInfo",
|
|
95
|
+
"DeviceAuthorization",
|
|
96
|
+
"EmailAccount",
|
|
97
|
+
"EmailAccountStore",
|
|
98
|
+
"EmailAgentToolsOff",
|
|
99
|
+
"EmailAttachmentNotFound",
|
|
100
|
+
"EmailAuthFailed",
|
|
101
|
+
"EmailClient",
|
|
102
|
+
"EmailContext",
|
|
103
|
+
"EmailDisabled",
|
|
104
|
+
"EmailError",
|
|
105
|
+
"EmailInvalidMessage",
|
|
106
|
+
"EmailInvalidSettings",
|
|
107
|
+
"EmailMailboxMissing",
|
|
108
|
+
"EmailMessageNotFound",
|
|
109
|
+
"EmailMessageTooLarge",
|
|
110
|
+
"EmailNotConfigured",
|
|
111
|
+
"EmailOAuthFailed",
|
|
112
|
+
"EmailOAuthPending",
|
|
113
|
+
"EmailOAuthReauthorize",
|
|
114
|
+
"EmailPolicyRefused",
|
|
115
|
+
"EmailProtocolError",
|
|
116
|
+
"EmailQuotaExceeded",
|
|
117
|
+
"EmailRateLimited",
|
|
118
|
+
"EmailReadOnlyViolation",
|
|
119
|
+
"EmailRecipientRefused",
|
|
120
|
+
"EmailSecret",
|
|
121
|
+
"EmailSecretUnavailable",
|
|
122
|
+
"EmailServerError",
|
|
123
|
+
"EmailSettings",
|
|
124
|
+
"EmailTlsFailed",
|
|
125
|
+
"EmailTransient",
|
|
126
|
+
"EmailUnreachable",
|
|
127
|
+
"FetchResult",
|
|
128
|
+
"ImapSettings",
|
|
129
|
+
"LoopbackAuthorization",
|
|
130
|
+
"MailCursor",
|
|
131
|
+
"MessageDetail",
|
|
132
|
+
"MessageSummary",
|
|
133
|
+
"OAuthSettings",
|
|
134
|
+
"OAuthTokenClient",
|
|
135
|
+
"OAuthTokenProvider",
|
|
136
|
+
"OutgoingMessage",
|
|
137
|
+
"PolicyDecision",
|
|
138
|
+
"RecipientPolicy",
|
|
139
|
+
"SearchCriteria",
|
|
140
|
+
"SecretVault",
|
|
141
|
+
"SendLimits",
|
|
142
|
+
"SendRateLimiter",
|
|
143
|
+
"SendResult",
|
|
144
|
+
"SmtpSettings",
|
|
145
|
+
"TokenSet",
|
|
146
|
+
"builtin_client",
|
|
147
|
+
"evaluate",
|
|
148
|
+
"guarded_send",
|
|
149
|
+
"normalize_address",
|
|
150
|
+
"normalize_domain",
|
|
151
|
+
"parse_recipients",
|
|
152
|
+
"provider_preset",
|
|
153
|
+
"resolve_oauth_client",
|
|
154
|
+
"tls_context",
|
|
155
|
+
"xoauth2_string",
|
|
156
|
+
]
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""IMAP BODYSTRUCTURE (RFC 3501 section 7.4.2): the MIME tree of a message without its content.
|
|
2
|
+
|
|
3
|
+
Reading a message fetches its structure first, then only the parts it needs by section
|
|
4
|
+
(`BODY.PEEK[1.2]`): the text/plain and text/html bodies for `read_email`, one part for an
|
|
5
|
+
attachment download. Attachments are listed from the structure (name, type, size) without
|
|
6
|
+
downloading them.
|
|
7
|
+
|
|
8
|
+
The parser follows the RFC grammar (parenthesised lists, strings, NIL); it never looks for
|
|
9
|
+
words in free text. Section numbers follow RFC 3501 section 6.4.5: the parts of a multipart
|
|
10
|
+
body are numbered 1, 2, ... (nested: 2.1, 2.2); a message that is not multipart has its body at
|
|
11
|
+
section 1; the parts inside an attached message (message/rfc822 at section 2) are 2.1, 2.2, ...
|
|
12
|
+
(its body is 2.1 when it is not multipart).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import binascii
|
|
19
|
+
import email.header
|
|
20
|
+
import email.utils
|
|
21
|
+
import quopri
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class MimePart:
|
|
28
|
+
"""One node of the structure. `size` is the part's size on the wire (encoded octets)."""
|
|
29
|
+
|
|
30
|
+
section: str
|
|
31
|
+
content_type: str
|
|
32
|
+
params: Dict[str, str] = field(default_factory=dict)
|
|
33
|
+
content_id: str = ""
|
|
34
|
+
encoding: str = ""
|
|
35
|
+
size: int = 0
|
|
36
|
+
disposition: str = ""
|
|
37
|
+
disposition_params: Dict[str, str] = field(default_factory=dict)
|
|
38
|
+
children: Tuple["MimePart", ...] = ()
|
|
39
|
+
# message/rfc822 only: the body of the attached message.
|
|
40
|
+
message_body: Optional["MimePart"] = None
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def multipart(self) -> bool:
|
|
44
|
+
return self.content_type.startswith("multipart/")
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def charset(self) -> str:
|
|
48
|
+
return self.params.get("charset", "")
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def filename(self) -> str:
|
|
52
|
+
return self.disposition_params.get("filename") or self.params.get("name") or ""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def is_attachment(self) -> bool:
|
|
56
|
+
"""Same rule as reading the whole message did: disposition `attachment`, or a file name."""
|
|
57
|
+
|
|
58
|
+
if self.multipart:
|
|
59
|
+
return False
|
|
60
|
+
return self.disposition == "attachment" or bool(self.filename)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _text(tok: Any) -> str:
|
|
64
|
+
if tok is None:
|
|
65
|
+
return ""
|
|
66
|
+
if isinstance(tok, (bytes, bytearray)):
|
|
67
|
+
return bytes(tok).decode("utf-8", errors="replace")
|
|
68
|
+
return str(tok)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _decode_words(value: str) -> str:
|
|
72
|
+
"""RFC 2047 encoded words (`=?utf-8?B?...?=`) inside a parameter value, if any."""
|
|
73
|
+
|
|
74
|
+
if "=?" not in value:
|
|
75
|
+
return value
|
|
76
|
+
try:
|
|
77
|
+
return str(email.header.make_header(email.header.decode_header(value)))
|
|
78
|
+
except (ValueError, LookupError, UnicodeDecodeError, binascii.Error):
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _params(tok: Any) -> Dict[str, str]:
|
|
83
|
+
"""A parameter list `("NAME" "value" ...)` -> {name: decoded value}.
|
|
84
|
+
|
|
85
|
+
RFC 2231 forms (`filename*`, continuations `filename*0*`) are decoded with the standard
|
|
86
|
+
library; RFC 2047 encoded words are decoded too.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
if not isinstance(tok, list):
|
|
90
|
+
return {}
|
|
91
|
+
pairs: List[Tuple[str, str]] = []
|
|
92
|
+
for i in range(0, len(tok) - 1, 2):
|
|
93
|
+
key = _text(tok[i]).strip().lower()
|
|
94
|
+
if key:
|
|
95
|
+
value = _text(tok[i + 1])
|
|
96
|
+
if not key.endswith("*"):
|
|
97
|
+
# decode_params unquotes; keep the value as the server sent it.
|
|
98
|
+
value = '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
99
|
+
pairs.append((key, value))
|
|
100
|
+
if not pairs:
|
|
101
|
+
return {}
|
|
102
|
+
try:
|
|
103
|
+
decoded = email.utils.decode_params([("", "")] + pairs)[1:]
|
|
104
|
+
except Exception: # noqa: BLE001 - a malformed parameter list reads as "no parameters"
|
|
105
|
+
decoded = [(k.rstrip("*"), v.strip('"')) for k, v in pairs]
|
|
106
|
+
out: Dict[str, str] = {}
|
|
107
|
+
for key, value in decoded:
|
|
108
|
+
if isinstance(value, tuple):
|
|
109
|
+
charset, language, encoded = value
|
|
110
|
+
text = email.utils.collapse_rfc2231_value((charset, language, email.utils.unquote(encoded)))
|
|
111
|
+
else:
|
|
112
|
+
text = _decode_words(email.utils.unquote(str(value)))
|
|
113
|
+
out[str(key).lower()] = str(text)
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _disposition(tok: Any) -> Tuple[str, Dict[str, str]]:
|
|
118
|
+
if isinstance(tok, list) and tok:
|
|
119
|
+
return _text(tok[0]).strip().lower(), _params(tok[1] if len(tok) > 1 else None)
|
|
120
|
+
return "", {}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _int(tok: Any) -> int:
|
|
124
|
+
try:
|
|
125
|
+
return int(_text(tok).strip())
|
|
126
|
+
except ValueError:
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _part_at(tok: Any, section: str) -> MimePart:
|
|
131
|
+
"""The part described by `tok`, located at `section`."""
|
|
132
|
+
|
|
133
|
+
if not isinstance(tok, list) or not tok:
|
|
134
|
+
raise ValueError("BODYSTRUCTURE part is not a list")
|
|
135
|
+
if isinstance(tok[0], list):
|
|
136
|
+
# multipart: (part)(part)... "SUBTYPE" [params [disposition [language [location]]]]
|
|
137
|
+
children: List[MimePart] = []
|
|
138
|
+
i = 0
|
|
139
|
+
while i < len(tok) and isinstance(tok[i], list):
|
|
140
|
+
children.append(_part_at(tok[i], f"{section}.{len(children) + 1}" if section else str(len(children) + 1)))
|
|
141
|
+
i += 1
|
|
142
|
+
subtype = _text(tok[i]).lower() if i < len(tok) else "mixed"
|
|
143
|
+
params = _params(tok[i + 1]) if i + 1 < len(tok) else {}
|
|
144
|
+
disp, dparams = _disposition(tok[i + 2]) if i + 2 < len(tok) else ("", {})
|
|
145
|
+
return MimePart(
|
|
146
|
+
section=section,
|
|
147
|
+
content_type=f"multipart/{subtype}",
|
|
148
|
+
params=params,
|
|
149
|
+
disposition=disp,
|
|
150
|
+
disposition_params=dparams,
|
|
151
|
+
children=tuple(children),
|
|
152
|
+
)
|
|
153
|
+
# single part: type subtype params id description encoding size ...
|
|
154
|
+
if len(tok) < 7:
|
|
155
|
+
raise ValueError("BODYSTRUCTURE part is too short")
|
|
156
|
+
ctype = f"{_text(tok[0]).lower()}/{_text(tok[1]).lower()}"
|
|
157
|
+
rest = list(tok[7:])
|
|
158
|
+
message_body: Optional[MimePart] = None
|
|
159
|
+
if ctype == "message/rfc822" and len(rest) >= 3:
|
|
160
|
+
# envelope, body, lines
|
|
161
|
+
message_body = _message_body(rest[1], section)
|
|
162
|
+
rest = rest[3:]
|
|
163
|
+
elif ctype.startswith("text/") and rest:
|
|
164
|
+
rest = rest[1:] # lines
|
|
165
|
+
# extension data: md5 disposition language location
|
|
166
|
+
disp, dparams = _disposition(rest[1]) if len(rest) > 1 else ("", {})
|
|
167
|
+
return MimePart(
|
|
168
|
+
section=section,
|
|
169
|
+
content_type=ctype,
|
|
170
|
+
params=_params(tok[2]),
|
|
171
|
+
content_id=_text(tok[3]).strip(),
|
|
172
|
+
encoding=_text(tok[5]).strip().lower(),
|
|
173
|
+
size=_int(tok[6]),
|
|
174
|
+
disposition=disp,
|
|
175
|
+
disposition_params=dparams,
|
|
176
|
+
message_body=message_body,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _message_body(tok: Any, prefix: str) -> MimePart:
|
|
181
|
+
"""The body of a message (the top level, or an attached message at section `prefix`)."""
|
|
182
|
+
|
|
183
|
+
if isinstance(tok, list) and tok and isinstance(tok[0], list):
|
|
184
|
+
return _part_at(tok, prefix)
|
|
185
|
+
return _part_at(tok, f"{prefix}.1" if prefix else "1")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_bodystructure(tok: Any) -> MimePart:
|
|
189
|
+
"""The root of a message's structure from the parsed `BODYSTRUCTURE` value."""
|
|
190
|
+
|
|
191
|
+
return _message_body(tok, "")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def walk(part: MimePart) -> Iterator[MimePart]:
|
|
195
|
+
"""Depth-first, in document order. An attached message (message/rfc822 with disposition
|
|
196
|
+
`attachment`) is one attachment; an inline one is walked into."""
|
|
197
|
+
|
|
198
|
+
yield part
|
|
199
|
+
for child in part.children:
|
|
200
|
+
yield from walk(child)
|
|
201
|
+
if part.message_body is not None and not part.is_attachment:
|
|
202
|
+
yield from walk(part.message_body)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def body_parts(root: MimePart) -> List[MimePart]:
|
|
206
|
+
"""The text/plain and text/html parts that are not attachments, in document order."""
|
|
207
|
+
|
|
208
|
+
return [
|
|
209
|
+
p
|
|
210
|
+
for p in walk(root)
|
|
211
|
+
if not p.multipart
|
|
212
|
+
and p.message_body is None
|
|
213
|
+
and not p.is_attachment
|
|
214
|
+
and p.content_type in ("text/plain", "text/html")
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def attachment_parts(root: MimePart) -> List[MimePart]:
|
|
219
|
+
return [p for p in walk(root) if p.is_attachment]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def decode_transfer(data: bytes, encoding: str) -> bytes:
|
|
223
|
+
"""Undo the Content-Transfer-Encoding of a part's wire bytes."""
|
|
224
|
+
|
|
225
|
+
enc = (encoding or "").strip().lower()
|
|
226
|
+
if enc == "base64":
|
|
227
|
+
try:
|
|
228
|
+
return base64.b64decode(data, validate=False)
|
|
229
|
+
except (binascii.Error, ValueError):
|
|
230
|
+
return base64.b64decode(data + b"=" * (-len(data) % 4), validate=False)
|
|
231
|
+
if enc == "quoted-printable":
|
|
232
|
+
return quopri.decodestring(data)
|
|
233
|
+
return bytes(data)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def decode_text(data: bytes, part: MimePart) -> str:
|
|
237
|
+
raw = decode_transfer(data, part.encoding)
|
|
238
|
+
charset = part.charset or "utf-8"
|
|
239
|
+
try:
|
|
240
|
+
return raw.decode(charset, errors="replace")
|
|
241
|
+
except LookupError:
|
|
242
|
+
return raw.decode("utf-8", errors="replace")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
__all__ = [
|
|
246
|
+
"MimePart",
|
|
247
|
+
"attachment_parts",
|
|
248
|
+
"body_parts",
|
|
249
|
+
"decode_text",
|
|
250
|
+
"decode_transfer",
|
|
251
|
+
"parse_bodystructure",
|
|
252
|
+
"walk",
|
|
253
|
+
]
|