immichpy 1.6.5__py3-none-any.whl
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.
- immichpy/.openapi-generator/FILES +387 -0
- immichpy/.openapi-generator/VERSION +1 -0
- immichpy/__init__.py +3 -0
- immichpy/cli/__main__.py +12 -0
- immichpy/cli/commands/activities.py +110 -0
- immichpy/cli/commands/albums.py +317 -0
- immichpy/cli/commands/api_keys.py +125 -0
- immichpy/cli/commands/assets.py +610 -0
- immichpy/cli/commands/authentication.py +343 -0
- immichpy/cli/commands/authentication_admin.py +34 -0
- immichpy/cli/commands/download.py +76 -0
- immichpy/cli/commands/duplicates.py +67 -0
- immichpy/cli/commands/faces.py +107 -0
- immichpy/cli/commands/jobs.py +75 -0
- immichpy/cli/commands/libraries.py +183 -0
- immichpy/cli/commands/maintenance_admin.py +59 -0
- immichpy/cli/commands/map.py +79 -0
- immichpy/cli/commands/memories.py +231 -0
- immichpy/cli/commands/notifications.py +152 -0
- immichpy/cli/commands/notifications_admin.py +127 -0
- immichpy/cli/commands/partners.py +108 -0
- immichpy/cli/commands/people.py +293 -0
- immichpy/cli/commands/plugins.py +46 -0
- immichpy/cli/commands/queues.py +115 -0
- immichpy/cli/commands/search.py +835 -0
- immichpy/cli/commands/server.py +237 -0
- immichpy/cli/commands/sessions.py +132 -0
- immichpy/cli/commands/shared_links.py +273 -0
- immichpy/cli/commands/stacks.py +153 -0
- immichpy/cli/commands/sync.py +140 -0
- immichpy/cli/commands/system_config.py +681 -0
- immichpy/cli/commands/system_metadata.py +91 -0
- immichpy/cli/commands/tags.py +191 -0
- immichpy/cli/commands/timeline.py +196 -0
- immichpy/cli/commands/trash.py +63 -0
- immichpy/cli/commands/users.py +406 -0
- immichpy/cli/commands/users_admin.py +444 -0
- immichpy/cli/commands/views.py +54 -0
- immichpy/cli/commands/workflows.py +140 -0
- immichpy/cli/consts.py +19 -0
- immichpy/cli/main.py +248 -0
- immichpy/cli/runtime.py +97 -0
- immichpy/cli/types.py +17 -0
- immichpy/cli/utils.py +227 -0
- immichpy/cli/wrapper/assets.py +223 -0
- immichpy/cli/wrapper/config.py +76 -0
- immichpy/cli/wrapper/download.py +77 -0
- immichpy/cli/wrapper/setup.py +90 -0
- immichpy/cli/wrapper/users.py +47 -0
- immichpy/client/.openapi-generator/FILES +371 -0
- immichpy/client/.openapi-generator/VERSION +1 -0
- immichpy/client/.openapi-generator-ignore +48 -0
- immichpy/client/__init__.py +0 -0
- immichpy/client/consts.py +1 -0
- immichpy/client/generated/__init__.py +1344 -0
- immichpy/client/generated/api/__init__.py +41 -0
- immichpy/client/generated/api/activities_api.py +1092 -0
- immichpy/client/generated/api/albums_api.py +3228 -0
- immichpy/client/generated/api/api_keys_api.py +1486 -0
- immichpy/client/generated/api/assets_api.py +5846 -0
- immichpy/client/generated/api/authentication_admin_api.py +254 -0
- immichpy/client/generated/api/authentication_api.py +3887 -0
- immichpy/client/generated/api/deprecated_api.py +2207 -0
- immichpy/client/generated/api/download_api.py +603 -0
- immichpy/client/generated/api/duplicates_api.py +743 -0
- immichpy/client/generated/api/faces_api.py +1055 -0
- immichpy/client/generated/api/jobs_api.py +787 -0
- immichpy/client/generated/api/libraries_api.py +2006 -0
- immichpy/client/generated/api/maintenance_admin_api.py +535 -0
- immichpy/client/generated/api/map_api.py +642 -0
- immichpy/client/generated/api/memories_api.py +2250 -0
- immichpy/client/generated/api/notifications_admin_api.py +812 -0
- immichpy/client/generated/api/notifications_api.py +1570 -0
- immichpy/client/generated/api/partners_api.py +1278 -0
- immichpy/client/generated/api/people_api.py +2905 -0
- immichpy/client/generated/api/plugins_api.py +503 -0
- immichpy/client/generated/api/queues_api.py +1292 -0
- immichpy/client/generated/api/search_api.py +3200 -0
- immichpy/client/generated/api/server_api.py +3474 -0
- immichpy/client/generated/api/sessions_api.py +1474 -0
- immichpy/client/generated/api/shared_links_api.py +2163 -0
- immichpy/client/generated/api/stacks_api.py +1769 -0
- immichpy/client/generated/api/sync_api.py +1514 -0
- immichpy/client/generated/api/system_config_api.py +967 -0
- immichpy/client/generated/api/system_metadata_api.py +966 -0
- immichpy/client/generated/api/tags_api.py +2298 -0
- immichpy/client/generated/api/timeline_api.py +1195 -0
- immichpy/client/generated/api/trash_api.py +739 -0
- immichpy/client/generated/api/users_admin_api.py +2613 -0
- immichpy/client/generated/api/users_api.py +3583 -0
- immichpy/client/generated/api/views_api.py +503 -0
- immichpy/client/generated/api/workflows_api.py +1257 -0
- immichpy/client/generated/api_client.py +756 -0
- immichpy/client/generated/api_response.py +20 -0
- immichpy/client/generated/configuration.py +638 -0
- immichpy/client/generated/exceptions.py +222 -0
- immichpy/client/generated/models/__init__.py +561 -0
- immichpy/client/generated/models/activity_create_dto.py +94 -0
- immichpy/client/generated/models/activity_response_dto.py +121 -0
- immichpy/client/generated/models/activity_statistics_response_dto.py +85 -0
- immichpy/client/generated/models/add_users_dto.py +101 -0
- immichpy/client/generated/models/admin_onboarding_update_dto.py +82 -0
- immichpy/client/generated/models/album_response_dto.py +201 -0
- immichpy/client/generated/models/album_statistics_response_dto.py +90 -0
- immichpy/client/generated/models/album_user_add_dto.py +87 -0
- immichpy/client/generated/models/album_user_create_dto.py +87 -0
- immichpy/client/generated/models/album_user_response_dto.py +95 -0
- immichpy/client/generated/models/album_user_role.py +34 -0
- immichpy/client/generated/models/albums_add_assets_dto.py +86 -0
- immichpy/client/generated/models/albums_add_assets_response_dto.py +86 -0
- immichpy/client/generated/models/albums_response.py +83 -0
- immichpy/client/generated/models/albums_update.py +85 -0
- immichpy/client/generated/models/api_key_create_dto.py +87 -0
- immichpy/client/generated/models/api_key_create_response_dto.py +94 -0
- immichpy/client/generated/models/api_key_response_dto.py +102 -0
- immichpy/client/generated/models/api_key_update_dto.py +87 -0
- immichpy/client/generated/models/asset_bulk_delete_dto.py +84 -0
- immichpy/client/generated/models/asset_bulk_update_dto.py +143 -0
- immichpy/client/generated/models/asset_bulk_upload_check_dto.py +100 -0
- immichpy/client/generated/models/asset_bulk_upload_check_item.py +85 -0
- immichpy/client/generated/models/asset_bulk_upload_check_response_dto.py +101 -0
- immichpy/client/generated/models/asset_bulk_upload_check_result.py +126 -0
- immichpy/client/generated/models/asset_copy_dto.py +113 -0
- immichpy/client/generated/models/asset_delta_sync_dto.py +87 -0
- immichpy/client/generated/models/asset_delta_sync_response_dto.py +102 -0
- immichpy/client/generated/models/asset_face_create_dto.py +110 -0
- immichpy/client/generated/models/asset_face_delete_dto.py +82 -0
- immichpy/client/generated/models/asset_face_response_dto.py +125 -0
- immichpy/client/generated/models/asset_face_update_dto.py +96 -0
- immichpy/client/generated/models/asset_face_update_item.py +86 -0
- immichpy/client/generated/models/asset_face_without_person_response_dto.py +111 -0
- immichpy/client/generated/models/asset_full_sync_dto.py +95 -0
- immichpy/client/generated/models/asset_ids_dto.py +83 -0
- immichpy/client/generated/models/asset_ids_response_dto.py +109 -0
- immichpy/client/generated/models/asset_job_name.py +36 -0
- immichpy/client/generated/models/asset_jobs_dto.py +87 -0
- immichpy/client/generated/models/asset_media_response_dto.py +84 -0
- immichpy/client/generated/models/asset_media_size.py +35 -0
- immichpy/client/generated/models/asset_media_status.py +35 -0
- immichpy/client/generated/models/asset_metadata_key.py +33 -0
- immichpy/client/generated/models/asset_metadata_response_dto.py +92 -0
- immichpy/client/generated/models/asset_metadata_upsert_dto.py +101 -0
- immichpy/client/generated/models/asset_metadata_upsert_item_dto.py +84 -0
- immichpy/client/generated/models/asset_ocr_response_dto.py +145 -0
- immichpy/client/generated/models/asset_order.py +34 -0
- immichpy/client/generated/models/asset_response_dto.py +293 -0
- immichpy/client/generated/models/asset_stack_response_dto.py +90 -0
- immichpy/client/generated/models/asset_stats_response_dto.py +90 -0
- immichpy/client/generated/models/asset_type_enum.py +36 -0
- immichpy/client/generated/models/asset_visibility.py +36 -0
- immichpy/client/generated/models/audio_codec.py +36 -0
- immichpy/client/generated/models/auth_status_response_dto.py +100 -0
- immichpy/client/generated/models/avatar_update.py +83 -0
- immichpy/client/generated/models/bulk_id_error_reason.py +36 -0
- immichpy/client/generated/models/bulk_id_response_dto.py +102 -0
- immichpy/client/generated/models/bulk_ids_dto.py +83 -0
- immichpy/client/generated/models/cast_response.py +88 -0
- immichpy/client/generated/models/cast_update.py +82 -0
- immichpy/client/generated/models/change_password_dto.py +101 -0
- immichpy/client/generated/models/check_existing_assets_dto.py +91 -0
- immichpy/client/generated/models/check_existing_assets_response_dto.py +82 -0
- immichpy/client/generated/models/clip_config.py +85 -0
- immichpy/client/generated/models/colorspace.py +34 -0
- immichpy/client/generated/models/contributor_count_response_dto.py +85 -0
- immichpy/client/generated/models/cq_mode.py +35 -0
- immichpy/client/generated/models/create_album_dto.py +112 -0
- immichpy/client/generated/models/create_library_dto.py +103 -0
- immichpy/client/generated/models/create_profile_image_response_dto.py +95 -0
- immichpy/client/generated/models/database_backup_config.py +94 -0
- immichpy/client/generated/models/download_archive_info.py +85 -0
- immichpy/client/generated/models/download_info_dto.py +96 -0
- immichpy/client/generated/models/download_response.py +90 -0
- immichpy/client/generated/models/download_response_dto.py +100 -0
- immichpy/client/generated/models/download_update.py +93 -0
- immichpy/client/generated/models/duplicate_detection_config.py +89 -0
- immichpy/client/generated/models/duplicate_response_dto.py +98 -0
- immichpy/client/generated/models/email_notifications_response.py +90 -0
- immichpy/client/generated/models/email_notifications_update.py +90 -0
- immichpy/client/generated/models/exif_response_dto.py +284 -0
- immichpy/client/generated/models/face_dto.py +83 -0
- immichpy/client/generated/models/facial_recognition_config.py +107 -0
- immichpy/client/generated/models/folders_response.py +92 -0
- immichpy/client/generated/models/folders_update.py +85 -0
- immichpy/client/generated/models/image_format.py +34 -0
- immichpy/client/generated/models/job_create_dto.py +83 -0
- immichpy/client/generated/models/job_name.py +87 -0
- immichpy/client/generated/models/job_settings_dto.py +83 -0
- immichpy/client/generated/models/library_response_dto.py +118 -0
- immichpy/client/generated/models/library_stats_response_dto.py +92 -0
- immichpy/client/generated/models/license_key_dto.py +98 -0
- immichpy/client/generated/models/license_response_dto.py +101 -0
- immichpy/client/generated/models/log_level.py +38 -0
- immichpy/client/generated/models/login_credential_dto.py +85 -0
- immichpy/client/generated/models/login_response_dto.py +109 -0
- immichpy/client/generated/models/logout_response_dto.py +85 -0
- immichpy/client/generated/models/machine_learning_availability_checks_dto.py +90 -0
- immichpy/client/generated/models/maintenance_action.py +34 -0
- immichpy/client/generated/models/maintenance_auth_dto.py +82 -0
- immichpy/client/generated/models/maintenance_login_dto.py +82 -0
- immichpy/client/generated/models/manual_job_name.py +38 -0
- immichpy/client/generated/models/map_marker_response_dto.py +111 -0
- immichpy/client/generated/models/map_reverse_geocode_response_dto.py +105 -0
- immichpy/client/generated/models/memories_response.py +92 -0
- immichpy/client/generated/models/memories_update.py +86 -0
- immichpy/client/generated/models/memory_create_dto.py +112 -0
- immichpy/client/generated/models/memory_response_dto.py +142 -0
- immichpy/client/generated/models/memory_search_order.py +35 -0
- immichpy/client/generated/models/memory_statistics_response_dto.py +82 -0
- immichpy/client/generated/models/memory_type.py +33 -0
- immichpy/client/generated/models/memory_update_dto.py +91 -0
- immichpy/client/generated/models/merge_person_dto.py +83 -0
- immichpy/client/generated/models/metadata_search_dto.py +277 -0
- immichpy/client/generated/models/notification_create_dto.py +120 -0
- immichpy/client/generated/models/notification_delete_all_dto.py +83 -0
- immichpy/client/generated/models/notification_dto.py +112 -0
- immichpy/client/generated/models/notification_level.py +36 -0
- immichpy/client/generated/models/notification_type.py +38 -0
- immichpy/client/generated/models/notification_update_all_dto.py +90 -0
- immichpy/client/generated/models/notification_update_dto.py +88 -0
- immichpy/client/generated/models/o_auth_authorize_response_dto.py +82 -0
- immichpy/client/generated/models/o_auth_callback_dto.py +90 -0
- immichpy/client/generated/models/o_auth_config_dto.py +90 -0
- immichpy/client/generated/models/o_auth_token_endpoint_auth_method.py +34 -0
- immichpy/client/generated/models/ocr_config.py +109 -0
- immichpy/client/generated/models/on_this_day_dto.py +86 -0
- immichpy/client/generated/models/onboarding_dto.py +82 -0
- immichpy/client/generated/models/onboarding_response_dto.py +82 -0
- immichpy/client/generated/models/partner_create_dto.py +83 -0
- immichpy/client/generated/models/partner_direction.py +34 -0
- immichpy/client/generated/models/partner_response_dto.py +108 -0
- immichpy/client/generated/models/partner_update_dto.py +82 -0
- immichpy/client/generated/models/people_response.py +92 -0
- immichpy/client/generated/models/people_response_dto.py +104 -0
- immichpy/client/generated/models/people_update.py +85 -0
- immichpy/client/generated/models/people_update_dto.py +96 -0
- immichpy/client/generated/models/people_update_item.py +128 -0
- immichpy/client/generated/models/permission.py +177 -0
- immichpy/client/generated/models/person_create_dto.py +117 -0
- immichpy/client/generated/models/person_response_dto.py +115 -0
- immichpy/client/generated/models/person_statistics_response_dto.py +82 -0
- immichpy/client/generated/models/person_update_dto.py +125 -0
- immichpy/client/generated/models/person_with_faces_response_dto.py +133 -0
- immichpy/client/generated/models/pin_code_change_dto.py +90 -0
- immichpy/client/generated/models/pin_code_reset_dto.py +85 -0
- immichpy/client/generated/models/pin_code_setup_dto.py +82 -0
- immichpy/client/generated/models/places_response_dto.py +100 -0
- immichpy/client/generated/models/plugin_action_response_dto.py +112 -0
- immichpy/client/generated/models/plugin_context.py +35 -0
- immichpy/client/generated/models/plugin_filter_response_dto.py +112 -0
- immichpy/client/generated/models/plugin_response_dto.py +143 -0
- immichpy/client/generated/models/plugin_trigger_type.py +34 -0
- immichpy/client/generated/models/purchase_response.py +88 -0
- immichpy/client/generated/models/purchase_update.py +92 -0
- immichpy/client/generated/models/queue_command.py +37 -0
- immichpy/client/generated/models/queue_command_dto.py +86 -0
- immichpy/client/generated/models/queue_delete_dto.py +85 -0
- immichpy/client/generated/models/queue_job_response_dto.py +93 -0
- immichpy/client/generated/models/queue_job_status.py +38 -0
- immichpy/client/generated/models/queue_name.py +49 -0
- immichpy/client/generated/models/queue_response_dto.py +97 -0
- immichpy/client/generated/models/queue_response_legacy_dto.py +102 -0
- immichpy/client/generated/models/queue_statistics_dto.py +103 -0
- immichpy/client/generated/models/queue_status_legacy_dto.py +85 -0
- immichpy/client/generated/models/queue_update_dto.py +82 -0
- immichpy/client/generated/models/queues_response_legacy_dto.py +244 -0
- immichpy/client/generated/models/random_search_dto.py +234 -0
- immichpy/client/generated/models/ratings_response.py +84 -0
- immichpy/client/generated/models/ratings_update.py +82 -0
- immichpy/client/generated/models/reaction_level.py +34 -0
- immichpy/client/generated/models/reaction_type.py +34 -0
- immichpy/client/generated/models/reverse_geocoding_state_response_dto.py +101 -0
- immichpy/client/generated/models/search_album_response_dto.py +116 -0
- immichpy/client/generated/models/search_asset_response_dto.py +129 -0
- immichpy/client/generated/models/search_explore_item.py +94 -0
- immichpy/client/generated/models/search_explore_response_dto.py +98 -0
- immichpy/client/generated/models/search_facet_count_response_dto.py +85 -0
- immichpy/client/generated/models/search_facet_response_dto.py +103 -0
- immichpy/client/generated/models/search_response_dto.py +104 -0
- immichpy/client/generated/models/search_statistics_response_dto.py +82 -0
- immichpy/client/generated/models/search_suggestion_type.py +38 -0
- immichpy/client/generated/models/server_about_response_dto.py +156 -0
- immichpy/client/generated/models/server_apk_links_dto.py +97 -0
- immichpy/client/generated/models/server_config_dto.py +118 -0
- immichpy/client/generated/models/server_features_dto.py +130 -0
- immichpy/client/generated/models/server_media_types_response_dto.py +90 -0
- immichpy/client/generated/models/server_ping_response.py +87 -0
- immichpy/client/generated/models/server_stats_response_dto.py +119 -0
- immichpy/client/generated/models/server_storage_response_dto.py +108 -0
- immichpy/client/generated/models/server_theme_dto.py +82 -0
- immichpy/client/generated/models/server_version_history_response_dto.py +91 -0
- immichpy/client/generated/models/server_version_response_dto.py +90 -0
- immichpy/client/generated/models/session_create_dto.py +96 -0
- immichpy/client/generated/models/session_create_response_dto.py +120 -0
- immichpy/client/generated/models/session_response_dto.py +117 -0
- immichpy/client/generated/models/session_unlock_dto.py +85 -0
- immichpy/client/generated/models/session_update_dto.py +84 -0
- immichpy/client/generated/models/set_maintenance_mode_dto.py +83 -0
- immichpy/client/generated/models/shared_link_create_dto.py +142 -0
- immichpy/client/generated/models/shared_link_edit_dto.py +134 -0
- immichpy/client/generated/models/shared_link_response_dto.py +173 -0
- immichpy/client/generated/models/shared_link_type.py +34 -0
- immichpy/client/generated/models/shared_links_response.py +92 -0
- immichpy/client/generated/models/shared_links_update.py +85 -0
- immichpy/client/generated/models/sign_up_dto.py +90 -0
- immichpy/client/generated/models/smart_search_dto.py +245 -0
- immichpy/client/generated/models/source_type.py +35 -0
- immichpy/client/generated/models/stack_create_dto.py +86 -0
- immichpy/client/generated/models/stack_response_dto.py +100 -0
- immichpy/client/generated/models/stack_update_dto.py +83 -0
- immichpy/client/generated/models/statistics_search_dto.py +217 -0
- immichpy/client/generated/models/sync_ack_delete_dto.py +83 -0
- immichpy/client/generated/models/sync_ack_dto.py +84 -0
- immichpy/client/generated/models/sync_ack_set_dto.py +83 -0
- immichpy/client/generated/models/sync_album_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_album_to_asset_delete_v1.py +85 -0
- immichpy/client/generated/models/sync_album_to_asset_v1.py +85 -0
- immichpy/client/generated/models/sync_album_user_delete_v1.py +85 -0
- immichpy/client/generated/models/sync_album_user_v1.py +91 -0
- immichpy/client/generated/models/sync_album_v1.py +122 -0
- immichpy/client/generated/models/sync_asset_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_asset_exif_v1.py +296 -0
- immichpy/client/generated/models/sync_asset_face_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_asset_face_v1.py +120 -0
- immichpy/client/generated/models/sync_asset_metadata_delete_v1.py +86 -0
- immichpy/client/generated/models/sync_asset_metadata_v1.py +91 -0
- immichpy/client/generated/models/sync_asset_v1.py +187 -0
- immichpy/client/generated/models/sync_auth_user_v1.py +154 -0
- immichpy/client/generated/models/sync_entity_type.py +79 -0
- immichpy/client/generated/models/sync_memory_asset_delete_v1.py +85 -0
- immichpy/client/generated/models/sync_memory_asset_v1.py +85 -0
- immichpy/client/generated/models/sync_memory_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_memory_v1.py +143 -0
- immichpy/client/generated/models/sync_partner_delete_v1.py +88 -0
- immichpy/client/generated/models/sync_partner_v1.py +90 -0
- immichpy/client/generated/models/sync_person_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_person_v1.py +131 -0
- immichpy/client/generated/models/sync_request_type.py +52 -0
- immichpy/client/generated/models/sync_stack_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_stack_v1.py +101 -0
- immichpy/client/generated/models/sync_stream_dto.py +86 -0
- immichpy/client/generated/models/sync_user_delete_v1.py +82 -0
- immichpy/client/generated/models/sync_user_metadata_delete_v1.py +84 -0
- immichpy/client/generated/models/sync_user_metadata_v1.py +91 -0
- immichpy/client/generated/models/sync_user_v1.py +118 -0
- immichpy/client/generated/models/system_config_backups_dto.py +92 -0
- immichpy/client/generated/models/system_config_dto.py +324 -0
- immichpy/client/generated/models/system_config_f_fmpeg_dto.py +156 -0
- immichpy/client/generated/models/system_config_faces_dto.py +82 -0
- immichpy/client/generated/models/system_config_generated_fullsize_image_dto.py +92 -0
- immichpy/client/generated/models/system_config_generated_image_dto.py +92 -0
- immichpy/client/generated/models/system_config_image_dto.py +124 -0
- immichpy/client/generated/models/system_config_job_dto.py +194 -0
- immichpy/client/generated/models/system_config_library_dto.py +104 -0
- immichpy/client/generated/models/system_config_library_scan_dto.py +85 -0
- immichpy/client/generated/models/system_config_library_watch_dto.py +82 -0
- immichpy/client/generated/models/system_config_logging_dto.py +86 -0
- immichpy/client/generated/models/system_config_machine_learning_dto.py +151 -0
- immichpy/client/generated/models/system_config_map_dto.py +90 -0
- immichpy/client/generated/models/system_config_metadata_dto.py +94 -0
- immichpy/client/generated/models/system_config_new_version_check_dto.py +82 -0
- immichpy/client/generated/models/system_config_nightly_tasks_dto.py +103 -0
- immichpy/client/generated/models/system_config_notifications_dto.py +92 -0
- immichpy/client/generated/models/system_config_o_auth_dto.py +155 -0
- immichpy/client/generated/models/system_config_password_login_dto.py +82 -0
- immichpy/client/generated/models/system_config_reverse_geocoding_dto.py +82 -0
- immichpy/client/generated/models/system_config_server_dto.py +94 -0
- immichpy/client/generated/models/system_config_smtp_dto.py +100 -0
- immichpy/client/generated/models/system_config_smtp_transport_dto.py +107 -0
- immichpy/client/generated/models/system_config_storage_template_dto.py +94 -0
- immichpy/client/generated/models/system_config_template_emails_dto.py +94 -0
- immichpy/client/generated/models/system_config_template_storage_option_dto.py +109 -0
- immichpy/client/generated/models/system_config_templates_dto.py +94 -0
- immichpy/client/generated/models/system_config_theme_dto.py +82 -0
- immichpy/client/generated/models/system_config_trash_dto.py +86 -0
- immichpy/client/generated/models/system_config_user_dto.py +83 -0
- immichpy/client/generated/models/tag_bulk_assets_dto.py +86 -0
- immichpy/client/generated/models/tag_bulk_assets_response_dto.py +82 -0
- immichpy/client/generated/models/tag_create_dto.py +111 -0
- immichpy/client/generated/models/tag_response_dto.py +107 -0
- immichpy/client/generated/models/tag_update_dto.py +87 -0
- immichpy/client/generated/models/tag_upsert_dto.py +82 -0
- immichpy/client/generated/models/tags_response.py +92 -0
- immichpy/client/generated/models/tags_update.py +85 -0
- immichpy/client/generated/models/template_dto.py +82 -0
- immichpy/client/generated/models/template_response_dto.py +83 -0
- immichpy/client/generated/models/test_email_response_dto.py +82 -0
- immichpy/client/generated/models/time_bucket_asset_response_dto.py +195 -0
- immichpy/client/generated/models/time_buckets_response_dto.py +88 -0
- immichpy/client/generated/models/tone_mapping.py +36 -0
- immichpy/client/generated/models/transcode_hw_accel.py +37 -0
- immichpy/client/generated/models/transcode_policy.py +37 -0
- immichpy/client/generated/models/trash_response_dto.py +82 -0
- immichpy/client/generated/models/update_album_dto.py +106 -0
- immichpy/client/generated/models/update_album_user_dto.py +83 -0
- immichpy/client/generated/models/update_asset_dto.py +135 -0
- immichpy/client/generated/models/update_library_dto.py +95 -0
- immichpy/client/generated/models/usage_by_user_dto.py +117 -0
- immichpy/client/generated/models/user_admin_create_dto.py +136 -0
- immichpy/client/generated/models/user_admin_delete_dto.py +82 -0
- immichpy/client/generated/models/user_admin_response_dto.py +176 -0
- immichpy/client/generated/models/user_admin_update_dto.py +141 -0
- immichpy/client/generated/models/user_avatar_color.py +42 -0
- immichpy/client/generated/models/user_license.py +91 -0
- immichpy/client/generated/models/user_metadata_key.py +35 -0
- immichpy/client/generated/models/user_preferences_response_dto.py +188 -0
- immichpy/client/generated/models/user_preferences_update_dto.py +199 -0
- immichpy/client/generated/models/user_response_dto.py +105 -0
- immichpy/client/generated/models/user_status.py +35 -0
- immichpy/client/generated/models/user_update_me_dto.py +98 -0
- immichpy/client/generated/models/validate_access_token_response_dto.py +82 -0
- immichpy/client/generated/models/validate_library_dto.py +93 -0
- immichpy/client/generated/models/validate_library_import_path_response_dto.py +92 -0
- immichpy/client/generated/models/validate_library_response_dto.py +103 -0
- immichpy/client/generated/models/version_check_state_response_dto.py +98 -0
- immichpy/client/generated/models/video_codec.py +36 -0
- immichpy/client/generated/models/video_container.py +36 -0
- immichpy/client/generated/models/workflow_action_item_dto.py +89 -0
- immichpy/client/generated/models/workflow_action_response_dto.py +105 -0
- immichpy/client/generated/models/workflow_create_dto.py +132 -0
- immichpy/client/generated/models/workflow_filter_item_dto.py +89 -0
- immichpy/client/generated/models/workflow_filter_response_dto.py +105 -0
- immichpy/client/generated/models/workflow_response_dto.py +163 -0
- immichpy/client/generated/models/workflow_update_dto.py +128 -0
- immichpy/client/generated/py.typed +0 -0
- immichpy/client/generated/rest.py +199 -0
- immichpy/client/main.py +375 -0
- immichpy/client/types.py +65 -0
- immichpy/client/utils/download.py +249 -0
- immichpy/client/utils/upload.py +462 -0
- immichpy/client/wrapper/__init__.py +0 -0
- immichpy/client/wrapper/assets_api_wrapped.py +254 -0
- immichpy/client/wrapper/download_api_wrapped.py +118 -0
- immichpy/client/wrapper/users_api_wrapped.py +54 -0
- immichpy/py.typed +0 -0
- immichpy-1.6.5.dist-info/METADATA +67 -0
- immichpy-1.6.5.dist-info/RECORD +439 -0
- immichpy-1.6.5.dist-info/WHEEL +4 -0
- immichpy-1.6.5.dist-info/entry_points.txt +3 -0
- immichpy-1.6.5.dist-info/licenses/LICENSE +9 -0
immichpy/cli/main.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Main CLI entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import typer
|
|
7
|
+
import click
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from immichpy.cli.consts import (
|
|
14
|
+
API_KEY_URL,
|
|
15
|
+
DEFAULT_FORMAT,
|
|
16
|
+
DEFAULT_PROFILE,
|
|
17
|
+
IMMICH_API_KEY,
|
|
18
|
+
IMMICH_ACCESS_TOKEN,
|
|
19
|
+
IMMICH_API_URL,
|
|
20
|
+
IMMICH_FORMAT,
|
|
21
|
+
IMMICH_PROFILE,
|
|
22
|
+
)
|
|
23
|
+
from immichpy.cli.utils import resolve_client_config, mask, print_
|
|
24
|
+
|
|
25
|
+
from immichpy import AsyncClient
|
|
26
|
+
from immichpy.cli.types import FormatMode, ClientConfig
|
|
27
|
+
|
|
28
|
+
# Import command modules
|
|
29
|
+
from immichpy.cli.commands import api_keys as api_keys_commands
|
|
30
|
+
from immichpy.cli.commands import activities as activities_commands
|
|
31
|
+
from immichpy.cli.commands import albums as albums_commands
|
|
32
|
+
from immichpy.cli.wrapper import assets as assets_wrapper
|
|
33
|
+
from immichpy.cli.commands import authentication as authentication_commands
|
|
34
|
+
from immichpy.cli.commands import authentication_admin as authentication_admin_commands
|
|
35
|
+
from immichpy.cli.wrapper import download as download_wrapper
|
|
36
|
+
from immichpy.cli.wrapper import config as config_commands
|
|
37
|
+
from immichpy.cli.wrapper import setup as setup_commands
|
|
38
|
+
from immichpy.cli.commands import duplicates as duplicates_commands
|
|
39
|
+
from immichpy.cli.commands import faces as faces_commands
|
|
40
|
+
from immichpy.cli.commands import jobs as jobs_commands
|
|
41
|
+
from immichpy.cli.commands import libraries as libraries_commands
|
|
42
|
+
from immichpy.cli.commands import maintenance_admin as maintenance_admin_commands
|
|
43
|
+
from immichpy.cli.commands import map as map_commands
|
|
44
|
+
from immichpy.cli.commands import memories as memories_commands
|
|
45
|
+
from immichpy.cli.commands import notifications as notifications_commands
|
|
46
|
+
from immichpy.cli.commands import notifications_admin as notifications_admin_commands
|
|
47
|
+
from immichpy.cli.commands import partners as partners_commands
|
|
48
|
+
from immichpy.cli.commands import people as people_commands
|
|
49
|
+
from immichpy.cli.commands import plugins as plugins_commands
|
|
50
|
+
from immichpy.cli.commands import queues as queues_commands
|
|
51
|
+
from immichpy.cli.commands import search as search_commands
|
|
52
|
+
from immichpy.cli.commands import server as server_commands
|
|
53
|
+
from immichpy.cli.commands import sessions as sessions_commands
|
|
54
|
+
from immichpy.cli.commands import shared_links as shared_links_commands
|
|
55
|
+
from immichpy.cli.commands import stacks as stacks_commands
|
|
56
|
+
from immichpy.cli.commands import sync as sync_commands
|
|
57
|
+
from immichpy.cli.commands import system_config as system_config_commands
|
|
58
|
+
from immichpy.cli.commands import system_metadata as system_metadata_commands
|
|
59
|
+
from immichpy.cli.commands import tags as tags_commands
|
|
60
|
+
from immichpy.cli.commands import timeline as timeline_commands
|
|
61
|
+
from immichpy.cli.commands import trash as trash_commands
|
|
62
|
+
from immichpy.cli.wrapper import users as users_wrapper
|
|
63
|
+
from immichpy.cli.commands import users_admin as users_admin_commands
|
|
64
|
+
from immichpy.cli.commands import views as views_commands
|
|
65
|
+
from immichpy.cli.commands import workflows as workflows_commands
|
|
66
|
+
|
|
67
|
+
# Global state
|
|
68
|
+
app = typer.Typer(
|
|
69
|
+
context_settings={"help_option_names": ["-h", "--help"]}, no_args_is_help=True
|
|
70
|
+
)
|
|
71
|
+
console = Console()
|
|
72
|
+
stderr_console = Console(file=sys.stderr)
|
|
73
|
+
|
|
74
|
+
# Add command modules to the main app
|
|
75
|
+
app.add_typer(api_keys_commands.app, name="api-keys", rich_help_panel="API commands")
|
|
76
|
+
app.add_typer(
|
|
77
|
+
activities_commands.app, name="activities", rich_help_panel="API commands"
|
|
78
|
+
)
|
|
79
|
+
app.add_typer(albums_commands.app, name="albums", rich_help_panel="API commands")
|
|
80
|
+
app.add_typer(assets_wrapper.app, name="assets", rich_help_panel="API commands")
|
|
81
|
+
app.add_typer(authentication_commands.app, name="auth", rich_help_panel="API commands")
|
|
82
|
+
app.add_typer(
|
|
83
|
+
authentication_admin_commands.app,
|
|
84
|
+
name="auth-admin",
|
|
85
|
+
rich_help_panel="API commands",
|
|
86
|
+
)
|
|
87
|
+
app.add_typer(download_wrapper.app, name="download", rich_help_panel="API commands")
|
|
88
|
+
app.add_typer(config_commands.app, name="config", rich_help_panel="Custom commands")
|
|
89
|
+
app.command(rich_help_panel="Custom commands")(setup_commands.setup)
|
|
90
|
+
app.add_typer(
|
|
91
|
+
duplicates_commands.app, name="duplicates", rich_help_panel="API commands"
|
|
92
|
+
)
|
|
93
|
+
app.add_typer(faces_commands.app, name="faces", rich_help_panel="API commands")
|
|
94
|
+
app.add_typer(jobs_commands.app, name="jobs", rich_help_panel="API commands")
|
|
95
|
+
app.add_typer(libraries_commands.app, name="libraries", rich_help_panel="API commands")
|
|
96
|
+
app.add_typer(
|
|
97
|
+
maintenance_admin_commands.app,
|
|
98
|
+
name="maintenance-admin",
|
|
99
|
+
rich_help_panel="API commands",
|
|
100
|
+
)
|
|
101
|
+
app.add_typer(map_commands.app, name="map", rich_help_panel="API commands")
|
|
102
|
+
app.add_typer(memories_commands.app, name="memories", rich_help_panel="API commands")
|
|
103
|
+
app.add_typer(
|
|
104
|
+
notifications_commands.app, name="notifications", rich_help_panel="API commands"
|
|
105
|
+
)
|
|
106
|
+
app.add_typer(
|
|
107
|
+
notifications_admin_commands.app,
|
|
108
|
+
name="notifications-admin",
|
|
109
|
+
rich_help_panel="API commands",
|
|
110
|
+
)
|
|
111
|
+
app.add_typer(partners_commands.app, name="partners", rich_help_panel="API commands")
|
|
112
|
+
app.add_typer(people_commands.app, name="people", rich_help_panel="API commands")
|
|
113
|
+
app.add_typer(plugins_commands.app, name="plugins", rich_help_panel="API commands")
|
|
114
|
+
app.add_typer(queues_commands.app, name="queues", rich_help_panel="API commands")
|
|
115
|
+
app.add_typer(search_commands.app, name="search", rich_help_panel="API commands")
|
|
116
|
+
app.add_typer(server_commands.app, name="server", rich_help_panel="API commands")
|
|
117
|
+
app.add_typer(sessions_commands.app, name="sessions", rich_help_panel="API commands")
|
|
118
|
+
app.add_typer(
|
|
119
|
+
shared_links_commands.app, name="shared-links", rich_help_panel="API commands"
|
|
120
|
+
)
|
|
121
|
+
app.add_typer(stacks_commands.app, name="stacks", rich_help_panel="API commands")
|
|
122
|
+
app.add_typer(sync_commands.app, name="sync", rich_help_panel="API commands")
|
|
123
|
+
app.add_typer(
|
|
124
|
+
system_config_commands.app, name="system-config", rich_help_panel="API commands"
|
|
125
|
+
)
|
|
126
|
+
app.add_typer(
|
|
127
|
+
system_metadata_commands.app, name="system-metadata", rich_help_panel="API commands"
|
|
128
|
+
)
|
|
129
|
+
app.add_typer(tags_commands.app, name="tags", rich_help_panel="API commands")
|
|
130
|
+
app.add_typer(timeline_commands.app, name="timeline", rich_help_panel="API commands")
|
|
131
|
+
app.add_typer(trash_commands.app, name="trash", rich_help_panel="API commands")
|
|
132
|
+
app.add_typer(users_wrapper.app, name="users", rich_help_panel="API commands")
|
|
133
|
+
app.add_typer(
|
|
134
|
+
users_admin_commands.app, name="users-admin", rich_help_panel="API commands"
|
|
135
|
+
)
|
|
136
|
+
app.add_typer(views_commands.app, name="views", rich_help_panel="API commands")
|
|
137
|
+
app.add_typer(workflows_commands.app, name="workflows", rich_help_panel="API commands")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def version_callback(value: bool) -> None: # pragma: no cover
|
|
141
|
+
if value:
|
|
142
|
+
print_(f"immich CLI (unofficial) {version('immich')}", type="text")
|
|
143
|
+
raise typer.Exit(0)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.callback(invoke_without_command=False)
|
|
147
|
+
def callback(
|
|
148
|
+
ctx: typer.Context,
|
|
149
|
+
verbose: bool = typer.Option(
|
|
150
|
+
False,
|
|
151
|
+
"--verbose",
|
|
152
|
+
"-v",
|
|
153
|
+
help="Show verbose output.",
|
|
154
|
+
),
|
|
155
|
+
format_mode: FormatMode = typer.Option(
|
|
156
|
+
DEFAULT_FORMAT,
|
|
157
|
+
"--format",
|
|
158
|
+
help="Output format of the CLI.",
|
|
159
|
+
envvar=IMMICH_FORMAT,
|
|
160
|
+
),
|
|
161
|
+
api_key: Optional[str] = typer.Option(
|
|
162
|
+
None,
|
|
163
|
+
"--api-key",
|
|
164
|
+
help=f"Authorize via API key (get one [link={API_KEY_URL}]here[/link]).",
|
|
165
|
+
envvar=IMMICH_API_KEY,
|
|
166
|
+
),
|
|
167
|
+
access_token: Optional[str] = typer.Option(
|
|
168
|
+
None,
|
|
169
|
+
"--access-token",
|
|
170
|
+
help="Authorize via access token.",
|
|
171
|
+
envvar=IMMICH_ACCESS_TOKEN,
|
|
172
|
+
),
|
|
173
|
+
base_url: Optional[str] = typer.Option(
|
|
174
|
+
None,
|
|
175
|
+
"--base-url",
|
|
176
|
+
help="The server to connect to.",
|
|
177
|
+
envvar=IMMICH_API_URL,
|
|
178
|
+
),
|
|
179
|
+
profile: str = typer.Option(
|
|
180
|
+
DEFAULT_PROFILE,
|
|
181
|
+
"--profile",
|
|
182
|
+
"-p",
|
|
183
|
+
envvar=IMMICH_PROFILE,
|
|
184
|
+
help="The profile to use.",
|
|
185
|
+
),
|
|
186
|
+
_version: bool = typer.Option(
|
|
187
|
+
False,
|
|
188
|
+
"--version",
|
|
189
|
+
callback=version_callback,
|
|
190
|
+
is_eager=True,
|
|
191
|
+
help="Show version and exit.",
|
|
192
|
+
),
|
|
193
|
+
) -> None: # pragma: no cover
|
|
194
|
+
ctx.ensure_object(dict)
|
|
195
|
+
ctx.obj["format"] = format_mode
|
|
196
|
+
ctx.obj["verbose"] = verbose
|
|
197
|
+
if ctx.invoked_subcommand is not None and ctx.invoked_subcommand not in [
|
|
198
|
+
"setup",
|
|
199
|
+
"config",
|
|
200
|
+
]:
|
|
201
|
+
config = resolve_client_config(
|
|
202
|
+
ClientConfig(
|
|
203
|
+
api_key=api_key,
|
|
204
|
+
access_token=access_token,
|
|
205
|
+
base_url=base_url,
|
|
206
|
+
),
|
|
207
|
+
profile=profile,
|
|
208
|
+
# we only consider the profile explicit if it was set via the command line
|
|
209
|
+
# environment variables are not considered explicit
|
|
210
|
+
profile_explicit=ctx.get_parameter_source("profile")
|
|
211
|
+
== click.core.ParameterSource.COMMANDLINE,
|
|
212
|
+
)
|
|
213
|
+
if not config.base_url:
|
|
214
|
+
print_(
|
|
215
|
+
"No base URL provided. Run 'immich setup' to set up a profile or use '--base-url' to specify a base URL.",
|
|
216
|
+
type="error",
|
|
217
|
+
)
|
|
218
|
+
raise typer.Exit(code=1)
|
|
219
|
+
if ctx.obj["verbose"]:
|
|
220
|
+
cli_vars = {
|
|
221
|
+
k: v
|
|
222
|
+
for k, v in ctx.params.items()
|
|
223
|
+
if k in ClientConfig.model_fields.keys() and v is not None
|
|
224
|
+
}
|
|
225
|
+
print_("Configuration used:", type="debug", ctx=ctx)
|
|
226
|
+
for field in ClientConfig.model_fields.keys():
|
|
227
|
+
value = getattr(config, field)
|
|
228
|
+
if field in ("api_key", "access_token") and value:
|
|
229
|
+
value = mask(value)
|
|
230
|
+
elif value is None:
|
|
231
|
+
value = "None"
|
|
232
|
+
source = "cli/env" if field in cli_vars else f"profile '{profile}'"
|
|
233
|
+
print_(f"- {field}: {value} (from {source})", type="debug", ctx=ctx)
|
|
234
|
+
if omit_access_token := (config.api_key is not None):
|
|
235
|
+
print_(
|
|
236
|
+
"Omitting access token because API key is provided.",
|
|
237
|
+
type="debug",
|
|
238
|
+
ctx=ctx,
|
|
239
|
+
)
|
|
240
|
+
ctx.obj["client"] = AsyncClient(
|
|
241
|
+
api_key=config.api_key,
|
|
242
|
+
access_token=None if omit_access_token else config.access_token,
|
|
243
|
+
base_url=config.base_url,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__": # pragma: no cover
|
|
248
|
+
app()
|
immichpy/cli/runtime.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Runtime helpers for executing async client calls and handling output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import traceback
|
|
8
|
+
from typing import Any, Awaitable, Callable
|
|
9
|
+
|
|
10
|
+
from immichpy.cli.utils import print_
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from typer import Context, Exit
|
|
13
|
+
|
|
14
|
+
from immichpy.cli.types import MaybeBaseModel
|
|
15
|
+
|
|
16
|
+
from immichpy import AsyncClient
|
|
17
|
+
from immichpy.client.generated.exceptions import ApiException
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_nested(d: dict[str, Any], path: list[str], value: Any) -> None:
|
|
21
|
+
"""Set a nested dictionary value using a path list.
|
|
22
|
+
|
|
23
|
+
Example: set_nested({}, ['user', 'name'], 'John') -> {'user': {'name': 'John'}}
|
|
24
|
+
"""
|
|
25
|
+
current = d
|
|
26
|
+
for part in path[:-1]:
|
|
27
|
+
if part not in current:
|
|
28
|
+
current[part] = {}
|
|
29
|
+
elif not isinstance(current[part], dict):
|
|
30
|
+
current[part] = {}
|
|
31
|
+
current = current[part]
|
|
32
|
+
current[path[-1]] = value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def print_response(data: MaybeBaseModel, ctx: Context) -> None:
|
|
36
|
+
"""Print response data."""
|
|
37
|
+
|
|
38
|
+
def convert_to_dict(obj: MaybeBaseModel) -> Any:
|
|
39
|
+
"""Recursively convert Pydantic models to dicts."""
|
|
40
|
+
if isinstance(obj, list):
|
|
41
|
+
return [convert_to_dict(item) for item in obj]
|
|
42
|
+
elif isinstance(obj, BaseModel):
|
|
43
|
+
return obj.model_dump()
|
|
44
|
+
else:
|
|
45
|
+
return obj
|
|
46
|
+
|
|
47
|
+
json_str = json.dumps(convert_to_dict(data), default=str)
|
|
48
|
+
|
|
49
|
+
print_(message=json_str, type="json", ctx=ctx)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def format_api_error(e: ApiException) -> tuple[str, int]:
|
|
53
|
+
"""Return (message, exit_code) for an ApiException."""
|
|
54
|
+
exit_code = 1 if e.status is None else e.status // 100
|
|
55
|
+
|
|
56
|
+
if not e.body:
|
|
57
|
+
return ("API error", exit_code)
|
|
58
|
+
|
|
59
|
+
if isinstance(e.body, str):
|
|
60
|
+
return (e.body, exit_code)
|
|
61
|
+
|
|
62
|
+
return (json.dumps(e.body, default=str), exit_code)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def run_async(coro: Awaitable[Any]) -> Any:
|
|
66
|
+
return await coro
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def run_command(
|
|
70
|
+
client: AsyncClient,
|
|
71
|
+
api_group: Any,
|
|
72
|
+
method_name: str,
|
|
73
|
+
ctx: Context | None = None,
|
|
74
|
+
**kwargs: Any,
|
|
75
|
+
) -> Any:
|
|
76
|
+
"""Run a client API method and handle the result."""
|
|
77
|
+
method: Callable[..., Awaitable[Any]] = getattr(api_group, method_name)
|
|
78
|
+
|
|
79
|
+
async def _call_and_close() -> Any:
|
|
80
|
+
try:
|
|
81
|
+
return await method(**kwargs)
|
|
82
|
+
finally:
|
|
83
|
+
await client.close()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
return asyncio.run(_call_and_close())
|
|
87
|
+
|
|
88
|
+
except ApiException as e:
|
|
89
|
+
message, code = format_api_error(e)
|
|
90
|
+
print_(message, type="error", ctx=ctx)
|
|
91
|
+
print_(traceback.format_exc(), type="debug", ctx=ctx)
|
|
92
|
+
raise Exit(code=code)
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
print_(f"Unexpected error: {str(e).strip()}", type="error", ctx=ctx)
|
|
96
|
+
print_(traceback.format_exc(), type="debug", ctx=ctx)
|
|
97
|
+
raise Exit(code=1)
|
immichpy/cli/types.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
MaybeBaseModel = Optional[Union[BaseModel, list[BaseModel], str]]
|
|
6
|
+
FormatMode = Literal["pretty", "json", "table"]
|
|
7
|
+
PrintType = Literal["info", "warning", "error", "debug", "success", "json", "text"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClientConfig(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
A configuration for a client to connect to an Immich server.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
base_url: Optional[str]
|
|
16
|
+
api_key: Optional[str]
|
|
17
|
+
access_token: Optional[str]
|
immichpy/cli/utils.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from typing import Any, Optional, cast, overload
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from rich import print as print_rich, print_json
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
import rtoml
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from immichpy.cli.consts import CONFIG_FILE, DEFAULT_FORMAT, SECRET_KEYS
|
|
10
|
+
from immichpy.cli.types import ClientConfig, FormatMode, PrintType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_path(data: dict[str, Any], path: str, value: Any) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Set a nested dictionary value using a path list.
|
|
16
|
+
|
|
17
|
+
Example: set_path({}, 'user.name', 'John') -> {'user': {'name': 'John'}}
|
|
18
|
+
"""
|
|
19
|
+
parts = path.split(".")
|
|
20
|
+
cur = data
|
|
21
|
+
for p in parts[:-1]:
|
|
22
|
+
cur = cur.setdefault(p, {})
|
|
23
|
+
cur[parts[-1]] = value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_path(data: dict[str, Any], path: str) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Get a nested dictionary value using a path list.
|
|
29
|
+
"""
|
|
30
|
+
parts = path.split(".")
|
|
31
|
+
cur = data
|
|
32
|
+
for p in parts:
|
|
33
|
+
cur = cur[p]
|
|
34
|
+
return cur
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_config() -> dict[str, Any]:
|
|
38
|
+
"""
|
|
39
|
+
Load the config file. Returns an empty dict if the file does not exist.
|
|
40
|
+
"""
|
|
41
|
+
if not CONFIG_FILE.exists():
|
|
42
|
+
return {}
|
|
43
|
+
return rtoml.load(CONFIG_FILE, none_value="")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_config(data: dict[str, Any]) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Write the config data to the config file and set permissions.
|
|
49
|
+
"""
|
|
50
|
+
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
with CONFIG_FILE.open("w") as f:
|
|
52
|
+
rtoml.dump(data, f, none_value="")
|
|
53
|
+
CONFIG_FILE.chmod(0o600)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_config() -> None:
|
|
57
|
+
"""
|
|
58
|
+
Check if the config file exists.
|
|
59
|
+
"""
|
|
60
|
+
if not CONFIG_FILE.exists():
|
|
61
|
+
print_(
|
|
62
|
+
"Config file does not exist. Run [bold]immich config set[/bold] to create it.",
|
|
63
|
+
type="error",
|
|
64
|
+
)
|
|
65
|
+
raise typer.Exit(code=1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def resolve_client_config(
|
|
69
|
+
config: ClientConfig, profile: str, profile_explicit: bool = False
|
|
70
|
+
) -> ClientConfig:
|
|
71
|
+
"""
|
|
72
|
+
Resolve the client config from the config file.
|
|
73
|
+
|
|
74
|
+
:param config: The config to resolve
|
|
75
|
+
:param profile: The profile to use
|
|
76
|
+
:param profile_explicit: Whether the profile was explicitly set by the user
|
|
77
|
+
:raises typer.Exit: If the profile is not found in the config
|
|
78
|
+
:return: The resolved config
|
|
79
|
+
"""
|
|
80
|
+
data = load_config()
|
|
81
|
+
profiles: dict[str, Any] = data.get("profiles", {})
|
|
82
|
+
|
|
83
|
+
if profile_explicit and profile not in profiles:
|
|
84
|
+
print_(
|
|
85
|
+
f"Profile '{profile}' not found. Run immich config set --profile {profile}.",
|
|
86
|
+
type="error",
|
|
87
|
+
)
|
|
88
|
+
raise typer.Exit(code=1)
|
|
89
|
+
|
|
90
|
+
profile_data = profiles.get(profile, {})
|
|
91
|
+
|
|
92
|
+
return ClientConfig(
|
|
93
|
+
api_key=config.api_key or profile_data.get("api_key"),
|
|
94
|
+
access_token=config.access_token or profile_data.get("access_token"),
|
|
95
|
+
base_url=config.base_url or profile_data.get("base_url"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _is_secret_key(key: str) -> bool:
|
|
100
|
+
"""Check if a key indicates a secret value."""
|
|
101
|
+
return any(secret in key.lower() for secret in SECRET_KEYS)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _redact_secret(secret: str, start: int = 3, end: int = 3) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Redact a secret by showing only the first `start` and last `end` characters.
|
|
107
|
+
|
|
108
|
+
:param secret: The secret string to redact
|
|
109
|
+
:param start: Number of characters to show at the start (default: 3)
|
|
110
|
+
:param end: Number of characters to show at the end (default: 3)
|
|
111
|
+
:return: The redacted secret string
|
|
112
|
+
"""
|
|
113
|
+
if not secret:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
length = len(secret)
|
|
117
|
+
if length <= start + end:
|
|
118
|
+
return "*" * length
|
|
119
|
+
|
|
120
|
+
return secret[:start] + "*" * (length - start - end) + secret[-end:]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@overload
|
|
124
|
+
def mask(secret: str, start: int = 3, end: int = 3) -> str: ...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@overload
|
|
128
|
+
def mask(obj: Any, start: int = 3, end: int = 3, key: Optional[str] = None) -> Any: ...
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def mask(obj: Any, start: int = 3, end: int = 3, key: Optional[str] = None) -> Any:
|
|
132
|
+
"""
|
|
133
|
+
Recursively mask secret values in nested dictionaries and lists.
|
|
134
|
+
|
|
135
|
+
:param obj: The object to mask (dict, list, or any other type)
|
|
136
|
+
:param start: Number of characters to show at the start (default: 3)
|
|
137
|
+
:param end: Number of characters to show at the end (default: 3)
|
|
138
|
+
:param key: Optional key path for top-level values to check if they're secrets
|
|
139
|
+
:return: The masked object with secrets redacted using _redact_secret
|
|
140
|
+
"""
|
|
141
|
+
if isinstance(obj, dict):
|
|
142
|
+
return {
|
|
143
|
+
k: (
|
|
144
|
+
_redact_secret(v, start, end)
|
|
145
|
+
if _is_secret_key(k) and isinstance(v, str)
|
|
146
|
+
else mask(v, start, end, k)
|
|
147
|
+
)
|
|
148
|
+
for k, v in obj.items()
|
|
149
|
+
}
|
|
150
|
+
if isinstance(obj, list):
|
|
151
|
+
return [mask(v, start, end) for v in obj]
|
|
152
|
+
if isinstance(obj, str):
|
|
153
|
+
if key is None or _is_secret_key(key):
|
|
154
|
+
return _redact_secret(obj, start, end)
|
|
155
|
+
return obj
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _print_table(data: Any) -> None: # pragma: no cover
|
|
159
|
+
"""
|
|
160
|
+
Print data as a table using rich.
|
|
161
|
+
For dicts: creates a table with key/value columns.
|
|
162
|
+
For lists: prints multiple tables.
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(data, list):
|
|
165
|
+
for item in data:
|
|
166
|
+
_print_table(item)
|
|
167
|
+
elif isinstance(data, dict):
|
|
168
|
+
_print_dict_table(data)
|
|
169
|
+
else:
|
|
170
|
+
print_rich(str(data))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _print_dict_table(data: dict[str, Any]) -> None: # pragma: no cover
|
|
174
|
+
"""Print a dictionary as a table with key and value columns."""
|
|
175
|
+
table = Table(show_header=True, header_style="bold")
|
|
176
|
+
table.add_column("Key", style="cyan")
|
|
177
|
+
table.add_column("Value", style="green")
|
|
178
|
+
|
|
179
|
+
for key, value in data.items():
|
|
180
|
+
table.add_row(key, str(value))
|
|
181
|
+
|
|
182
|
+
print_rich(table)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def print_(
|
|
186
|
+
message: str,
|
|
187
|
+
*,
|
|
188
|
+
type: PrintType,
|
|
189
|
+
ctx: Optional[typer.Context] = None,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Print a message in the given format as a rich print or a plain print.
|
|
193
|
+
:param message: The message to print
|
|
194
|
+
:param type: The type of the message
|
|
195
|
+
:param ctx: The context to use
|
|
196
|
+
"""
|
|
197
|
+
match type:
|
|
198
|
+
case "json":
|
|
199
|
+
try:
|
|
200
|
+
format_mode = cast(FormatMode, ctx.obj.get("format", DEFAULT_FORMAT)) # type: ignore[possibly-missing-attribute]
|
|
201
|
+
except (AttributeError, KeyError):
|
|
202
|
+
format_mode = DEFAULT_FORMAT
|
|
203
|
+
match format_mode:
|
|
204
|
+
case "pretty":
|
|
205
|
+
print_json(message)
|
|
206
|
+
case "json":
|
|
207
|
+
print(message)
|
|
208
|
+
case "table": # pragma: no cover
|
|
209
|
+
try:
|
|
210
|
+
data = json.loads(message)
|
|
211
|
+
_print_table(data)
|
|
212
|
+
except json.JSONDecodeError:
|
|
213
|
+
print(message)
|
|
214
|
+
print_("The 'table' format is experimental.", type="warning")
|
|
215
|
+
case "text":
|
|
216
|
+
print(message)
|
|
217
|
+
case "info":
|
|
218
|
+
print_rich(message)
|
|
219
|
+
case "warning":
|
|
220
|
+
print_rich(f"[yellow][bold][Warning][/bold] {message}[/yellow]")
|
|
221
|
+
case "error":
|
|
222
|
+
print_rich(f"[red][bold][Error][/bold] {message}[/red]")
|
|
223
|
+
case "success":
|
|
224
|
+
print_rich(f"[green][bold][Success][/bold][/green] {message}")
|
|
225
|
+
case "debug":
|
|
226
|
+
if ctx is not None and ctx.obj["verbose"]:
|
|
227
|
+
print_rich(f"[blue][bold][Debug][/bold] {message}[/blue]")
|