holder-python 0.2.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.
- holder/__init__.py +21 -0
- holder/client.py +182 -0
- holder/collections.py +70 -0
- holder/events.py +238 -0
- holder/exceptions.py +35 -0
- holder/generated/__init__.py +8 -0
- holder/generated/api/__init__.py +1 -0
- holder/generated/api/default/__init__.py +1 -0
- holder/generated/api/default/delete_ai_messages_message_id.py +166 -0
- holder/generated/api/default/delete_ai_messages_message_id_links.py +205 -0
- holder/generated/api/default/delete_ai_providers_credentials_provider.py +168 -0
- holder/generated/api/default/delete_ai_threads_thread_id.py +166 -0
- holder/generated/api/default/delete_cards_card_id.py +225 -0
- holder/generated/api/default/delete_cards_card_id_links.py +205 -0
- holder/generated/api/default/delete_cards_card_id_milestones_milestone_id.py +175 -0
- holder/generated/api/default/delete_cards_card_id_resources.py +197 -0
- holder/generated/api/default/delete_cards_card_id_tags.py +197 -0
- holder/generated/api/default/delete_locations_location_id.py +105 -0
- holder/generated/api/default/delete_locations_location_id_binding.py +102 -0
- holder/generated/api/default/delete_projects_project_id.py +187 -0
- holder/generated/api/default/delete_resources_resource_id.py +161 -0
- holder/generated/api/default/delete_trash.py +181 -0
- holder/generated/api/default/delete_trash_type_id.py +175 -0
- holder/generated/api/default/get_ai_capabilities.py +166 -0
- holder/generated/api/default/get_ai_catalog_json.py +134 -0
- holder/generated/api/default/get_ai_catalog_yaml.py +132 -0
- holder/generated/api/default/get_ai_local_models_config.py +134 -0
- holder/generated/api/default/get_ai_messages.py +186 -0
- holder/generated/api/default/get_ai_messages_message_id.py +166 -0
- holder/generated/api/default/get_ai_messages_message_id_backlinks.py +194 -0
- holder/generated/api/default/get_ai_messages_message_id_links.py +194 -0
- holder/generated/api/default/get_ai_providers_catalog.py +139 -0
- holder/generated/api/default/get_ai_providers_credentials.py +136 -0
- holder/generated/api/default/get_ai_runner_pull_job_id.py +171 -0
- holder/generated/api/default/get_ai_runner_pull_job_id_events.py +218 -0
- holder/generated/api/default/get_ai_runs.py +186 -0
- holder/generated/api/default/get_ai_runs_run_id.py +166 -0
- holder/generated/api/default/get_ai_runs_run_id_events.py +213 -0
- holder/generated/api/default/get_ai_status.py +134 -0
- holder/generated/api/default/get_ai_threads.py +171 -0
- holder/generated/api/default/get_ai_threads_thread_id.py +166 -0
- holder/generated/api/default/get_calendar.py +221 -0
- holder/generated/api/default/get_cards.py +286 -0
- holder/generated/api/default/get_cards_card_id.py +166 -0
- holder/generated/api/default/get_cards_card_id_backlinks.py +194 -0
- holder/generated/api/default/get_cards_card_id_links.py +194 -0
- holder/generated/api/default/get_cards_card_id_milestones.py +173 -0
- holder/generated/api/default/get_cards_context.py +226 -0
- holder/generated/api/default/get_event_cursor.py +198 -0
- holder/generated/api/default/get_git_providers_json.py +134 -0
- holder/generated/api/default/get_git_providers_yaml.py +132 -0
- holder/generated/api/default/get_health.py +134 -0
- holder/generated/api/default/get_imports_job_id.py +159 -0
- holder/generated/api/default/get_locations.py +160 -0
- holder/generated/api/default/get_locations_location_id.py +157 -0
- holder/generated/api/default/get_openapi_yaml.py +132 -0
- holder/generated/api/default/get_ping.py +128 -0
- holder/generated/api/default/get_projects.py +261 -0
- holder/generated/api/default/get_projects_project_id.py +166 -0
- holder/generated/api/default/get_projects_project_id_cards.py +321 -0
- holder/generated/api/default/get_projects_project_id_encryption_check.py +166 -0
- holder/generated/api/default/get_projects_project_id_git_sync_status.py +171 -0
- holder/generated/api/default/get_projects_project_id_history.py +246 -0
- holder/generated/api/default/get_projects_project_id_history_cards_card_id.py +243 -0
- holder/generated/api/default/get_projects_project_id_history_cards_card_id_compare.py +286 -0
- holder/generated/api/default/get_projects_project_id_history_cards_card_id_snapshot.py +225 -0
- holder/generated/api/default/get_projects_project_id_tags.py +171 -0
- holder/generated/api/default/get_resources.py +242 -0
- holder/generated/api/default/get_resources_resource_id.py +161 -0
- holder/generated/api/default/get_resources_resource_id_assets_asset_id_content.py +247 -0
- holder/generated/api/default/get_search_ai.py +216 -0
- holder/generated/api/default/get_search_cards.py +216 -0
- holder/generated/api/default/get_trash.py +181 -0
- holder/generated/api/default/patch_ai_messages_message_id.py +192 -0
- holder/generated/api/default/patch_ai_threads_thread_id.py +192 -0
- holder/generated/api/default/patch_cards_card_id.py +192 -0
- holder/generated/api/default/patch_cards_card_id_milestones_milestone_id.py +214 -0
- holder/generated/api/default/patch_locations_location_id.py +117 -0
- holder/generated/api/default/patch_projects_project_id.py +192 -0
- holder/generated/api/default/patch_resources_resource_id.py +192 -0
- holder/generated/api/default/post_ai_messages.py +176 -0
- holder/generated/api/default/post_ai_messages_capture.py +181 -0
- holder/generated/api/default/post_ai_messages_message_id_links.py +192 -0
- holder/generated/api/default/post_ai_messages_message_id_restore.py +166 -0
- holder/generated/api/default/post_ai_runner_pull.py +181 -0
- holder/generated/api/default/post_ai_runner_retry.py +139 -0
- holder/generated/api/default/post_ai_runs.py +194 -0
- holder/generated/api/default/post_ai_threads.py +176 -0
- holder/generated/api/default/post_bye.py +166 -0
- holder/generated/api/default/post_card_references_resolve.py +181 -0
- holder/generated/api/default/post_cards.py +176 -0
- holder/generated/api/default/post_cards_card_id_links.py +192 -0
- holder/generated/api/default/post_cards_card_id_milestones.py +199 -0
- holder/generated/api/default/post_cards_card_id_move.py +222 -0
- holder/generated/api/default/post_cards_card_id_resources.py +197 -0
- holder/generated/api/default/post_cards_card_id_restore.py +186 -0
- holder/generated/api/default/post_cards_card_id_tags.py +197 -0
- holder/generated/api/default/post_imports.py +160 -0
- holder/generated/api/default/post_locations.py +160 -0
- holder/generated/api/default/post_locations_location_id_test.py +105 -0
- holder/generated/api/default/post_projects.py +171 -0
- holder/generated/api/default/post_projects_project_id_git_pull.py +211 -0
- holder/generated/api/default/post_projects_project_id_git_push.py +193 -0
- holder/generated/api/default/post_projects_project_id_git_sync.py +207 -0
- holder/generated/api/default/post_projects_project_id_git_test_remote.py +205 -0
- holder/generated/api/default/post_projects_project_id_history_cards_card_id_restore.py +240 -0
- holder/generated/api/default/post_projects_project_id_recovery_token_export.py +196 -0
- holder/generated/api/default/post_projects_project_id_recovery_token_import.py +196 -0
- holder/generated/api/default/post_projects_project_id_repair_hierarchy.py +240 -0
- holder/generated/api/default/post_rebuild.py +176 -0
- holder/generated/api/default/post_recovery_token_import.py +185 -0
- holder/generated/api/default/post_reindex.py +139 -0
- holder/generated/api/default/post_resources.py +176 -0
- holder/generated/api/default/put_ai_local_models_config.py +173 -0
- holder/generated/api/default/put_ai_providers_credentials.py +175 -0
- holder/generated/api/default/put_locations_location_id_binding.py +117 -0
- holder/generated/api/default/put_locations_preferred.py +107 -0
- holder/generated/api/default/stream_changes.py +256 -0
- holder/generated/client.py +280 -0
- holder/generated/errors.py +16 -0
- holder/generated/models/__init__.py +569 -0
- holder/generated/models/ai_local_model_config_data.py +111 -0
- holder/generated/models/ai_local_model_config_response.py +78 -0
- holder/generated/models/ai_local_model_config_update_request.py +134 -0
- holder/generated/models/ai_message.py +204 -0
- holder/generated/models/ai_message_capture_request.py +246 -0
- holder/generated/models/ai_message_capture_request_context_type_0.py +46 -0
- holder/generated/models/ai_message_capture_response.py +78 -0
- holder/generated/models/ai_message_capture_response_data.py +78 -0
- holder/generated/models/ai_message_create_request.py +186 -0
- holder/generated/models/ai_message_create_response.py +78 -0
- holder/generated/models/ai_message_create_response_data.py +62 -0
- holder/generated/models/ai_message_get_response.py +76 -0
- holder/generated/models/ai_message_list_response.py +84 -0
- holder/generated/models/ai_message_update_request.py +203 -0
- holder/generated/models/ai_message_update_response.py +78 -0
- holder/generated/models/ai_message_update_response_data.py +62 -0
- holder/generated/models/ai_provider_catalog_provider.py +265 -0
- holder/generated/models/ai_provider_catalog_provider_api_type_0.py +46 -0
- holder/generated/models/ai_provider_catalog_provider_auth_type_0.py +46 -0
- holder/generated/models/ai_provider_catalog_provider_models_item.py +46 -0
- holder/generated/models/ai_provider_catalog_response.py +78 -0
- holder/generated/models/ai_provider_catalog_response_data.py +100 -0
- holder/generated/models/ai_provider_credential.py +94 -0
- holder/generated/models/ai_provider_credential_delete_response.py +80 -0
- holder/generated/models/ai_provider_credential_delete_response_data.py +62 -0
- holder/generated/models/ai_provider_credential_upsert_request.py +92 -0
- holder/generated/models/ai_provider_credential_upsert_response.py +78 -0
- holder/generated/models/ai_provider_credentials_list_response.py +80 -0
- holder/generated/models/ai_provider_credentials_list_response_data.py +78 -0
- holder/generated/models/ai_run_create_request.py +133 -0
- holder/generated/models/ai_run_create_request_context.py +46 -0
- holder/generated/models/ai_run_event_data.py +146 -0
- holder/generated/models/ai_run_get_response.py +76 -0
- holder/generated/models/ai_run_item.py +328 -0
- holder/generated/models/ai_run_item_policy_trace_type_0.py +46 -0
- holder/generated/models/ai_run_list_response.py +84 -0
- holder/generated/models/ai_status_data.py +186 -0
- holder/generated/models/ai_status_pull_job.py +130 -0
- holder/generated/models/ai_status_response.py +76 -0
- holder/generated/models/ai_thread.py +116 -0
- holder/generated/models/ai_thread_create_request.py +119 -0
- holder/generated/models/ai_thread_create_response.py +78 -0
- holder/generated/models/ai_thread_create_response_data.py +62 -0
- holder/generated/models/ai_thread_get_response.py +76 -0
- holder/generated/models/ai_thread_list_response.py +84 -0
- holder/generated/models/ai_thread_patch_response.py +78 -0
- holder/generated/models/ai_thread_patch_response_data.py +62 -0
- holder/generated/models/ai_thread_update_request.py +104 -0
- holder/generated/models/asset_import_job.py +141 -0
- holder/generated/models/asset_import_job_response.py +76 -0
- holder/generated/models/asset_import_job_status.py +13 -0
- holder/generated/models/asset_import_request.py +86 -0
- holder/generated/models/asset_placement.py +96 -0
- holder/generated/models/asset_placement_encoding.py +9 -0
- holder/generated/models/bye_response.py +62 -0
- holder/generated/models/calendar_card_activity.py +86 -0
- holder/generated/models/card_context_breadcrumb.py +113 -0
- holder/generated/models/card_context_breadcrumb_type.py +9 -0
- holder/generated/models/card_context_card.py +161 -0
- holder/generated/models/card_context_data.py +124 -0
- holder/generated/models/card_context_data_project.py +70 -0
- holder/generated/models/card_context_response.py +76 -0
- holder/generated/models/card_create_request.py +147 -0
- holder/generated/models/card_create_response.py +78 -0
- holder/generated/models/card_create_response_data.py +70 -0
- holder/generated/models/card_data.py +160 -0
- holder/generated/models/card_get_response.py +76 -0
- holder/generated/models/card_history_author.py +70 -0
- holder/generated/models/card_history_comparison_data.py +110 -0
- holder/generated/models/card_history_comparison_response.py +78 -0
- holder/generated/models/card_history_diff_line.py +100 -0
- holder/generated/models/card_history_diff_line_origin.py +10 -0
- holder/generated/models/card_history_entry.py +172 -0
- holder/generated/models/card_history_entry_kind.py +16 -0
- holder/generated/models/card_history_entry_saves_item.py +94 -0
- holder/generated/models/card_history_page_data.py +114 -0
- holder/generated/models/card_history_page_response.py +76 -0
- holder/generated/models/card_history_restore_response.py +80 -0
- holder/generated/models/card_history_restore_response_data.py +108 -0
- holder/generated/models/card_history_snapshot_response.py +80 -0
- holder/generated/models/card_history_snapshot_response_data.py +76 -0
- holder/generated/models/card_history_version.py +86 -0
- holder/generated/models/card_link.py +116 -0
- holder/generated/models/card_link_create_request.py +111 -0
- holder/generated/models/card_link_create_response.py +76 -0
- holder/generated/models/card_link_delete_request.py +80 -0
- holder/generated/models/card_link_list_response.py +84 -0
- holder/generated/models/card_list_item.py +161 -0
- holder/generated/models/card_list_response.py +84 -0
- holder/generated/models/card_move_intent.py +15 -0
- holder/generated/models/card_move_request.py +134 -0
- holder/generated/models/card_move_response.py +76 -0
- holder/generated/models/card_move_result.py +114 -0
- holder/generated/models/card_page.py +90 -0
- holder/generated/models/card_page_item.py +170 -0
- holder/generated/models/card_page_response.py +76 -0
- holder/generated/models/card_patch_response.py +78 -0
- holder/generated/models/card_patch_response_data.py +62 -0
- holder/generated/models/card_reference_candidate.py +84 -0
- holder/generated/models/card_reference_resolve_data.py +129 -0
- holder/generated/models/card_reference_resolve_data_match_kind.py +10 -0
- holder/generated/models/card_reference_resolve_data_status.py +10 -0
- holder/generated/models/card_reference_resolve_request.py +82 -0
- holder/generated/models/card_reference_resolve_request_scope.py +10 -0
- holder/generated/models/card_reference_resolve_response.py +78 -0
- holder/generated/models/card_tag_mutation_request.py +70 -0
- holder/generated/models/card_tag_mutation_response.py +78 -0
- holder/generated/models/card_tag_mutation_result.py +88 -0
- holder/generated/models/card_tag_mutation_result_outcome.py +12 -0
- holder/generated/models/card_update_request.py +122 -0
- holder/generated/models/caste_info.py +85 -0
- holder/generated/models/caste_info_name.py +12 -0
- holder/generated/models/caste_recommended_model.py +143 -0
- holder/generated/models/caste_recommended_model_required_caste.py +12 -0
- holder/generated/models/change_event_data.py +109 -0
- holder/generated/models/change_event_data_entity.py +14 -0
- holder/generated/models/delete_cards_card_id_children.py +9 -0
- holder/generated/models/delete_cards_card_id_resources_body.py +70 -0
- holder/generated/models/error.py +70 -0
- holder/generated/models/error_response.py +76 -0
- holder/generated/models/event_checkpoint.py +92 -0
- holder/generated/models/event_checkpoint_git_revisions.py +66 -0
- holder/generated/models/event_checkpoint_history_urls.py +48 -0
- holder/generated/models/event_checkpoint_response.py +76 -0
- holder/generated/models/event_resync_data.py +116 -0
- holder/generated/models/event_resync_data_reason.py +9 -0
- holder/generated/models/get_ai_catalog_json_response_200.py +46 -0
- holder/generated/models/get_ai_messages_message_id_backlinks_include_deleted.py +9 -0
- holder/generated/models/get_ai_messages_message_id_links_include_deleted.py +9 -0
- holder/generated/models/get_cards_card_id_backlinks_include_deleted.py +9 -0
- holder/generated/models/get_cards_card_id_links_include_deleted.py +9 -0
- holder/generated/models/get_cards_context_order.py +10 -0
- holder/generated/models/get_cards_order.py +10 -0
- holder/generated/models/get_cards_view.py +9 -0
- holder/generated/models/get_git_providers_json_response_200.py +46 -0
- holder/generated/models/get_locations_location_id_response_200.py +46 -0
- holder/generated/models/get_ping_response_200.py +8 -0
- holder/generated/models/get_projects_order.py +13 -0
- holder/generated/models/get_projects_project_id_cards_order.py +9 -0
- holder/generated/models/get_projects_project_id_cards_parent_type_0.py +8 -0
- holder/generated/models/get_projects_project_id_history_cards_card_id_compare_mode.py +9 -0
- holder/generated/models/get_projects_project_id_history_kind.py +13 -0
- holder/generated/models/health_data.py +110 -0
- holder/generated/models/health_data_crypt_filter.py +78 -0
- holder/generated/models/health_response.py +76 -0
- holder/generated/models/hierarchy_repair.py +100 -0
- holder/generated/models/hierarchy_repair_move.py +76 -0
- holder/generated/models/hierarchy_repair_request.py +62 -0
- holder/generated/models/hierarchy_repair_response.py +76 -0
- holder/generated/models/local_model.py +91 -0
- holder/generated/models/location_binding_request.py +81 -0
- holder/generated/models/location_binding_request_values.py +46 -0
- holder/generated/models/milestone.py +177 -0
- holder/generated/models/milestone_create_request.py +135 -0
- holder/generated/models/milestone_list_response.py +84 -0
- holder/generated/models/milestone_remove_response.py +78 -0
- holder/generated/models/milestone_remove_result.py +79 -0
- holder/generated/models/milestone_response.py +76 -0
- holder/generated/models/milestone_update_request.py +116 -0
- holder/generated/models/patch_locations_location_id_body.py +90 -0
- holder/generated/models/patch_locations_location_id_body_configuration.py +46 -0
- holder/generated/models/post_ai_runner_pull_body.py +62 -0
- holder/generated/models/post_cards_card_id_resources_body.py +70 -0
- holder/generated/models/post_locations_response_201.py +46 -0
- holder/generated/models/project.py +197 -0
- holder/generated/models/project_calendar_response.py +78 -0
- holder/generated/models/project_calendar_response_data.py +136 -0
- holder/generated/models/project_create_request.py +177 -0
- holder/generated/models/project_create_request_privacy_mode.py +9 -0
- holder/generated/models/project_create_response.py +78 -0
- holder/generated/models/project_create_response_data.py +62 -0
- holder/generated/models/project_delete_response.py +78 -0
- holder/generated/models/project_delete_response_data.py +62 -0
- holder/generated/models/project_encryption_check.py +94 -0
- holder/generated/models/project_encryption_check_response.py +80 -0
- holder/generated/models/project_encryption_check_response_data.py +92 -0
- holder/generated/models/project_encryption_check_response_data_privacy_mode.py +9 -0
- holder/generated/models/project_get_response.py +76 -0
- holder/generated/models/project_git_push_request.py +95 -0
- holder/generated/models/project_git_push_response.py +78 -0
- holder/generated/models/project_git_push_response_data.py +199 -0
- holder/generated/models/project_git_push_response_data_status.py +15 -0
- holder/generated/models/project_git_sync_operation_response.py +80 -0
- holder/generated/models/project_git_sync_operation_response_data.py +163 -0
- holder/generated/models/project_git_sync_operation_response_data_pull.py +96 -0
- holder/generated/models/project_git_sync_operation_response_data_pull_status.py +11 -0
- holder/generated/models/project_git_sync_operation_response_data_push.py +118 -0
- holder/generated/models/project_git_sync_operation_response_data_push_status.py +15 -0
- holder/generated/models/project_git_sync_operation_response_data_status.py +9 -0
- holder/generated/models/project_git_sync_request.py +94 -0
- holder/generated/models/project_git_sync_status_response.py +80 -0
- holder/generated/models/project_git_sync_status_response_data.py +76 -0
- holder/generated/models/project_git_test_remote_request.py +96 -0
- holder/generated/models/project_git_test_remote_response.py +80 -0
- holder/generated/models/project_git_test_remote_response_data.py +151 -0
- holder/generated/models/project_git_test_remote_response_data_status.py +14 -0
- holder/generated/models/project_history_activity.py +138 -0
- holder/generated/models/project_history_affected_object.py +74 -0
- holder/generated/models/project_history_affected_object_kind.py +13 -0
- holder/generated/models/project_history_page_data.py +115 -0
- holder/generated/models/project_history_page_response.py +78 -0
- holder/generated/models/project_list_response.py +84 -0
- holder/generated/models/project_privacy_mode.py +9 -0
- holder/generated/models/project_recovery_token_export_request.py +62 -0
- holder/generated/models/project_recovery_token_export_response.py +80 -0
- holder/generated/models/project_recovery_token_export_response_data.py +78 -0
- holder/generated/models/project_recovery_token_import_request.py +70 -0
- holder/generated/models/project_recovery_token_import_response.py +80 -0
- holder/generated/models/project_recovery_token_import_response_data.py +62 -0
- holder/generated/models/project_sync.py +227 -0
- holder/generated/models/project_tag.py +70 -0
- holder/generated/models/project_tag_list_response.py +84 -0
- holder/generated/models/project_update_request.py +181 -0
- holder/generated/models/project_update_request_privacy_mode.py +9 -0
- holder/generated/models/project_update_response.py +78 -0
- holder/generated/models/project_update_response_data.py +75 -0
- holder/generated/models/put_locations_preferred_body.py +70 -0
- holder/generated/models/rebuild_data.py +94 -0
- holder/generated/models/rebuild_request.py +62 -0
- holder/generated/models/rebuild_response.py +76 -0
- holder/generated/models/recovery_token_import_auto_response.py +80 -0
- holder/generated/models/recovery_token_import_auto_response_data.py +141 -0
- holder/generated/models/recovery_token_import_auto_response_data_pull_status.py +10 -0
- holder/generated/models/reindex_data.py +62 -0
- holder/generated/models/reindex_response.py +76 -0
- holder/generated/models/resource.py +134 -0
- holder/generated/models/resource_asset.py +140 -0
- holder/generated/models/resource_attachment_response.py +80 -0
- holder/generated/models/resource_attachment_response_data.py +90 -0
- holder/generated/models/resource_attachment_response_data_outcome.py +11 -0
- holder/generated/models/resource_create_request.py +129 -0
- holder/generated/models/resource_delete_response.py +78 -0
- holder/generated/models/resource_delete_response_data.py +62 -0
- holder/generated/models/resource_list_response.py +134 -0
- holder/generated/models/resource_metadata.py +55 -0
- holder/generated/models/resource_response.py +76 -0
- holder/generated/models/resource_update_request.py +102 -0
- holder/generated/models/runner_capabilities_data.py +220 -0
- holder/generated/models/runner_capabilities_response.py +78 -0
- holder/generated/models/runner_pull_job.py +122 -0
- holder/generated/models/runner_pull_progress.py +86 -0
- holder/generated/models/runner_pull_start_response.py +78 -0
- holder/generated/models/runner_pull_start_response_data.py +78 -0
- holder/generated/models/runner_pull_status_response.py +76 -0
- holder/generated/models/search_card_item.py +102 -0
- holder/generated/models/search_cards_response.py +84 -0
- holder/generated/models/search_message_item.py +86 -0
- holder/generated/models/search_messages_response.py +84 -0
- holder/generated/models/storage_location.py +149 -0
- holder/generated/models/storage_location_configuration.py +48 -0
- holder/generated/models/storage_location_list_response.py +100 -0
- holder/generated/models/storage_location_provider.py +9 -0
- holder/generated/models/storage_location_request.py +118 -0
- holder/generated/models/storage_location_request_configuration.py +46 -0
- holder/generated/models/storage_location_request_provider.py +9 -0
- holder/generated/models/trash_delete_response.py +78 -0
- holder/generated/models/trash_delete_response_data.py +46 -0
- holder/generated/models/trash_item.py +211 -0
- holder/generated/models/trash_list_response.py +84 -0
- holder/generated/types.py +54 -0
- holder/library.py +15 -0
- holder/models/__init__.py +15 -0
- holder/models/card.py +272 -0
- holder/models/project.py +207 -0
- holder/models/search.py +66 -0
- holder/models/tag.py +117 -0
- holder/py.typed +0 -0
- holder_python-0.2.5.dist-info/METADATA +120 -0
- holder_python-0.2.5.dist-info/RECORD +391 -0
- holder_python-0.2.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any, TypeVar, cast
|
|
5
|
+
|
|
6
|
+
from attrs import define as _attrs_define
|
|
7
|
+
from attrs import field as _attrs_field
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from ..types import UNSET, Unset
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", bound="TrashItem")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@_attrs_define
|
|
16
|
+
class TrashItem:
|
|
17
|
+
"""
|
|
18
|
+
Attributes:
|
|
19
|
+
type_ (str):
|
|
20
|
+
card_id (None | str | Unset):
|
|
21
|
+
message_id (None | str | Unset):
|
|
22
|
+
thread_id (None | str | Unset):
|
|
23
|
+
project_id (str | Unset):
|
|
24
|
+
title (None | str | Unset):
|
|
25
|
+
role (None | str | Unset):
|
|
26
|
+
deleted_at (int | Unset):
|
|
27
|
+
created_at (int | Unset):
|
|
28
|
+
rel_path (None | str | Unset):
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
type_: str
|
|
32
|
+
card_id: None | str | Unset = UNSET
|
|
33
|
+
message_id: None | str | Unset = UNSET
|
|
34
|
+
thread_id: None | str | Unset = UNSET
|
|
35
|
+
project_id: str | Unset = UNSET
|
|
36
|
+
title: None | str | Unset = UNSET
|
|
37
|
+
role: None | str | Unset = UNSET
|
|
38
|
+
deleted_at: int | Unset = UNSET
|
|
39
|
+
created_at: int | Unset = UNSET
|
|
40
|
+
rel_path: None | str | Unset = UNSET
|
|
41
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict[str, Any]:
|
|
44
|
+
type_ = self.type_
|
|
45
|
+
|
|
46
|
+
card_id: None | str | Unset
|
|
47
|
+
if isinstance(self.card_id, Unset):
|
|
48
|
+
card_id = UNSET
|
|
49
|
+
else:
|
|
50
|
+
card_id = self.card_id
|
|
51
|
+
|
|
52
|
+
message_id: None | str | Unset
|
|
53
|
+
if isinstance(self.message_id, Unset):
|
|
54
|
+
message_id = UNSET
|
|
55
|
+
else:
|
|
56
|
+
message_id = self.message_id
|
|
57
|
+
|
|
58
|
+
thread_id: None | str | Unset
|
|
59
|
+
if isinstance(self.thread_id, Unset):
|
|
60
|
+
thread_id = UNSET
|
|
61
|
+
else:
|
|
62
|
+
thread_id = self.thread_id
|
|
63
|
+
|
|
64
|
+
project_id = self.project_id
|
|
65
|
+
|
|
66
|
+
title: None | str | Unset
|
|
67
|
+
if isinstance(self.title, Unset):
|
|
68
|
+
title = UNSET
|
|
69
|
+
else:
|
|
70
|
+
title = self.title
|
|
71
|
+
|
|
72
|
+
role: None | str | Unset
|
|
73
|
+
if isinstance(self.role, Unset):
|
|
74
|
+
role = UNSET
|
|
75
|
+
else:
|
|
76
|
+
role = self.role
|
|
77
|
+
|
|
78
|
+
deleted_at = self.deleted_at
|
|
79
|
+
|
|
80
|
+
created_at = self.created_at
|
|
81
|
+
|
|
82
|
+
rel_path: None | str | Unset
|
|
83
|
+
if isinstance(self.rel_path, Unset):
|
|
84
|
+
rel_path = UNSET
|
|
85
|
+
else:
|
|
86
|
+
rel_path = self.rel_path
|
|
87
|
+
|
|
88
|
+
field_dict: dict[str, Any] = {}
|
|
89
|
+
field_dict.update(self.additional_properties)
|
|
90
|
+
field_dict.update(
|
|
91
|
+
{
|
|
92
|
+
"type": type_,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
if card_id is not UNSET:
|
|
96
|
+
field_dict["card_id"] = card_id
|
|
97
|
+
if message_id is not UNSET:
|
|
98
|
+
field_dict["message_id"] = message_id
|
|
99
|
+
if thread_id is not UNSET:
|
|
100
|
+
field_dict["thread_id"] = thread_id
|
|
101
|
+
if project_id is not UNSET:
|
|
102
|
+
field_dict["project_id"] = project_id
|
|
103
|
+
if title is not UNSET:
|
|
104
|
+
field_dict["title"] = title
|
|
105
|
+
if role is not UNSET:
|
|
106
|
+
field_dict["role"] = role
|
|
107
|
+
if deleted_at is not UNSET:
|
|
108
|
+
field_dict["deleted_at"] = deleted_at
|
|
109
|
+
if created_at is not UNSET:
|
|
110
|
+
field_dict["created_at"] = created_at
|
|
111
|
+
if rel_path is not UNSET:
|
|
112
|
+
field_dict["rel_path"] = rel_path
|
|
113
|
+
|
|
114
|
+
return field_dict
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_dict(cls, src_dict: Mapping[str, Any]) -> Self:
|
|
118
|
+
d = dict(src_dict)
|
|
119
|
+
type_ = d.pop("type")
|
|
120
|
+
|
|
121
|
+
def _parse_card_id(data: object) -> None | str | Unset:
|
|
122
|
+
if data is None:
|
|
123
|
+
return data
|
|
124
|
+
if isinstance(data, Unset):
|
|
125
|
+
return data
|
|
126
|
+
return cast(None | str | Unset, data)
|
|
127
|
+
|
|
128
|
+
card_id = _parse_card_id(d.pop("card_id", UNSET))
|
|
129
|
+
|
|
130
|
+
def _parse_message_id(data: object) -> None | str | Unset:
|
|
131
|
+
if data is None:
|
|
132
|
+
return data
|
|
133
|
+
if isinstance(data, Unset):
|
|
134
|
+
return data
|
|
135
|
+
return cast(None | str | Unset, data)
|
|
136
|
+
|
|
137
|
+
message_id = _parse_message_id(d.pop("message_id", UNSET))
|
|
138
|
+
|
|
139
|
+
def _parse_thread_id(data: object) -> None | str | Unset:
|
|
140
|
+
if data is None:
|
|
141
|
+
return data
|
|
142
|
+
if isinstance(data, Unset):
|
|
143
|
+
return data
|
|
144
|
+
return cast(None | str | Unset, data)
|
|
145
|
+
|
|
146
|
+
thread_id = _parse_thread_id(d.pop("thread_id", UNSET))
|
|
147
|
+
|
|
148
|
+
project_id = d.pop("project_id", UNSET)
|
|
149
|
+
|
|
150
|
+
def _parse_title(data: object) -> None | str | Unset:
|
|
151
|
+
if data is None:
|
|
152
|
+
return data
|
|
153
|
+
if isinstance(data, Unset):
|
|
154
|
+
return data
|
|
155
|
+
return cast(None | str | Unset, data)
|
|
156
|
+
|
|
157
|
+
title = _parse_title(d.pop("title", UNSET))
|
|
158
|
+
|
|
159
|
+
def _parse_role(data: object) -> None | str | Unset:
|
|
160
|
+
if data is None:
|
|
161
|
+
return data
|
|
162
|
+
if isinstance(data, Unset):
|
|
163
|
+
return data
|
|
164
|
+
return cast(None | str | Unset, data)
|
|
165
|
+
|
|
166
|
+
role = _parse_role(d.pop("role", UNSET))
|
|
167
|
+
|
|
168
|
+
deleted_at = d.pop("deleted_at", UNSET)
|
|
169
|
+
|
|
170
|
+
created_at = d.pop("created_at", UNSET)
|
|
171
|
+
|
|
172
|
+
def _parse_rel_path(data: object) -> None | str | Unset:
|
|
173
|
+
if data is None:
|
|
174
|
+
return data
|
|
175
|
+
if isinstance(data, Unset):
|
|
176
|
+
return data
|
|
177
|
+
return cast(None | str | Unset, data)
|
|
178
|
+
|
|
179
|
+
rel_path = _parse_rel_path(d.pop("rel_path", UNSET))
|
|
180
|
+
|
|
181
|
+
trash_item = cls(
|
|
182
|
+
type_=type_,
|
|
183
|
+
card_id=card_id,
|
|
184
|
+
message_id=message_id,
|
|
185
|
+
thread_id=thread_id,
|
|
186
|
+
project_id=project_id,
|
|
187
|
+
title=title,
|
|
188
|
+
role=role,
|
|
189
|
+
deleted_at=deleted_at,
|
|
190
|
+
created_at=created_at,
|
|
191
|
+
rel_path=rel_path,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
trash_item.additional_properties = d
|
|
195
|
+
return trash_item
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def additional_keys(self) -> list[str]:
|
|
199
|
+
return list(self.additional_properties.keys())
|
|
200
|
+
|
|
201
|
+
def __getitem__(self, key: str) -> Any:
|
|
202
|
+
return self.additional_properties[key]
|
|
203
|
+
|
|
204
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
205
|
+
self.additional_properties[key] = value
|
|
206
|
+
|
|
207
|
+
def __delitem__(self, key: str) -> None:
|
|
208
|
+
del self.additional_properties[key]
|
|
209
|
+
|
|
210
|
+
def __contains__(self, key: str) -> bool:
|
|
211
|
+
return key in self.additional_properties
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from attrs import define as _attrs_define
|
|
7
|
+
from attrs import field as _attrs_field
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..models.trash_item import TrashItem
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", bound="TrashListResponse")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@_attrs_define
|
|
18
|
+
class TrashListResponse:
|
|
19
|
+
"""
|
|
20
|
+
Attributes:
|
|
21
|
+
ok (bool):
|
|
22
|
+
data (list[TrashItem]):
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
ok: bool
|
|
26
|
+
data: list[TrashItem]
|
|
27
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
ok = self.ok
|
|
31
|
+
|
|
32
|
+
data = []
|
|
33
|
+
for data_item_data in self.data:
|
|
34
|
+
data_item = data_item_data.to_dict()
|
|
35
|
+
data.append(data_item)
|
|
36
|
+
|
|
37
|
+
field_dict: dict[str, Any] = {}
|
|
38
|
+
field_dict.update(self.additional_properties)
|
|
39
|
+
field_dict.update(
|
|
40
|
+
{
|
|
41
|
+
"ok": ok,
|
|
42
|
+
"data": data,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return field_dict
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_dict(cls, src_dict: Mapping[str, Any]) -> Self:
|
|
50
|
+
from ..models.trash_item import TrashItem
|
|
51
|
+
|
|
52
|
+
d = dict(src_dict)
|
|
53
|
+
ok = d.pop("ok")
|
|
54
|
+
|
|
55
|
+
data = []
|
|
56
|
+
_data = d.pop("data")
|
|
57
|
+
for data_item_data in _data:
|
|
58
|
+
data_item = TrashItem.from_dict(data_item_data)
|
|
59
|
+
|
|
60
|
+
data.append(data_item)
|
|
61
|
+
|
|
62
|
+
trash_list_response = cls(
|
|
63
|
+
ok=ok,
|
|
64
|
+
data=data,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
trash_list_response.additional_properties = d
|
|
68
|
+
return trash_list_response
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def additional_keys(self) -> list[str]:
|
|
72
|
+
return list(self.additional_properties.keys())
|
|
73
|
+
|
|
74
|
+
def __getitem__(self, key: str) -> Any:
|
|
75
|
+
return self.additional_properties[key]
|
|
76
|
+
|
|
77
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
78
|
+
self.additional_properties[key] = value
|
|
79
|
+
|
|
80
|
+
def __delitem__(self, key: str) -> None:
|
|
81
|
+
del self.additional_properties[key]
|
|
82
|
+
|
|
83
|
+
def __contains__(self, key: str) -> bool:
|
|
84
|
+
return key in self.additional_properties
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Contains some shared types for properties"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, MutableMapping
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
from typing import IO, BinaryIO, Generic, Literal, TypeVar
|
|
6
|
+
|
|
7
|
+
from attrs import define
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Unset:
|
|
11
|
+
def __bool__(self) -> Literal[False]:
|
|
12
|
+
return False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
UNSET: Unset = Unset()
|
|
16
|
+
|
|
17
|
+
# The types that `httpx.Client(files=)` can accept, copied from that library.
|
|
18
|
+
FileContent = IO[bytes] | bytes | str
|
|
19
|
+
FileTypes = (
|
|
20
|
+
# (filename, file (or bytes), content_type)
|
|
21
|
+
tuple[str | None, FileContent, str | None]
|
|
22
|
+
# (filename, file (or bytes), content_type, headers)
|
|
23
|
+
| tuple[str | None, FileContent, str | None, Mapping[str, str]]
|
|
24
|
+
)
|
|
25
|
+
RequestFiles = list[tuple[str, FileTypes]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@define
|
|
29
|
+
class File:
|
|
30
|
+
"""Contains information for file uploads"""
|
|
31
|
+
|
|
32
|
+
payload: BinaryIO
|
|
33
|
+
file_name: str | None = None
|
|
34
|
+
mime_type: str | None = None
|
|
35
|
+
|
|
36
|
+
def to_tuple(self) -> FileTypes:
|
|
37
|
+
"""Return a tuple representation that httpx will accept for multipart/form-data"""
|
|
38
|
+
return self.file_name, self.payload, self.mime_type
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
T = TypeVar("T")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@define
|
|
45
|
+
class Response(Generic[T]):
|
|
46
|
+
"""A response from an endpoint"""
|
|
47
|
+
|
|
48
|
+
status_code: HTTPStatus
|
|
49
|
+
content: bytes
|
|
50
|
+
headers: MutableMapping[str, str]
|
|
51
|
+
parsed: T | None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"]
|
holder/library.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Compatibility imports for the original synchronous library module."""
|
|
2
|
+
|
|
3
|
+
from .client import Holder
|
|
4
|
+
from .collections import Collection
|
|
5
|
+
from .models.card import Card, CardCollection
|
|
6
|
+
from .models.project import Project, ProjectCollection
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Card",
|
|
10
|
+
"CardCollection",
|
|
11
|
+
"Collection",
|
|
12
|
+
"Holder",
|
|
13
|
+
"Project",
|
|
14
|
+
"ProjectCollection",
|
|
15
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Project, card, tag and search result models for the Holder client."""
|
|
2
|
+
|
|
3
|
+
from .card import Card
|
|
4
|
+
from .project import HierarchyRepair, Project, RepairedCard
|
|
5
|
+
from .search import CardSearchResult
|
|
6
|
+
from .tag import Tag
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Card",
|
|
10
|
+
"CardSearchResult",
|
|
11
|
+
"HierarchyRepair",
|
|
12
|
+
"Project",
|
|
13
|
+
"RepairedCard",
|
|
14
|
+
"Tag",
|
|
15
|
+
]
|
holder/models/card.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Cards and their project-scoped synchronous collection queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass, field, fields
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
9
|
+
|
|
10
|
+
from ..collections import Collection
|
|
11
|
+
from ..exceptions import NotFoundError, ProtocolError
|
|
12
|
+
from ..generated.api.default import (
|
|
13
|
+
delete_cards_card_id,
|
|
14
|
+
get_cards_card_id,
|
|
15
|
+
get_projects_project_id_cards,
|
|
16
|
+
patch_cards_card_id,
|
|
17
|
+
post_cards,
|
|
18
|
+
post_cards_card_id_move,
|
|
19
|
+
post_cards_card_id_restore,
|
|
20
|
+
)
|
|
21
|
+
from ..generated.models import (
|
|
22
|
+
CardCreateRequest,
|
|
23
|
+
CardMoveIntent,
|
|
24
|
+
CardMoveRequest,
|
|
25
|
+
CardUpdateRequest,
|
|
26
|
+
DeleteCardsCardIdChildren,
|
|
27
|
+
)
|
|
28
|
+
from ..generated.models.get_projects_project_id_cards_parent_type_0 import (
|
|
29
|
+
GetProjectsProjectIdCardsParentType0 as CardParent,
|
|
30
|
+
)
|
|
31
|
+
from ..generated.types import UNSET, Unset
|
|
32
|
+
from .search import CardSearchCollection
|
|
33
|
+
from .tag import CardTagCollection
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from .project import Project
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CardCollection(Collection["Card"]):
|
|
40
|
+
def __init__(self, project: Project, query: dict[str, Any] | None = None) -> None:
|
|
41
|
+
self._project = project
|
|
42
|
+
super().__init__(project._holder, query)
|
|
43
|
+
|
|
44
|
+
def create(self, *, title: str, content: str = "") -> Card:
|
|
45
|
+
"""Create a root card, or a child when called on a children collection.
|
|
46
|
+
|
|
47
|
+
Tag filters do not add tags to the content. Cached listings remain
|
|
48
|
+
unchanged until refreshed. The write is never retried; if the subsequent
|
|
49
|
+
fetch fails, the card may already exist.
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(title, str):
|
|
52
|
+
raise TypeError("title must be a string")
|
|
53
|
+
if not isinstance(content, str):
|
|
54
|
+
raise TypeError("content must be a string")
|
|
55
|
+
parent = self._query.get("parent", UNSET)
|
|
56
|
+
if parent == CardParent.ROOTS:
|
|
57
|
+
parent = UNSET
|
|
58
|
+
data = self._holder._call(
|
|
59
|
+
post_cards,
|
|
60
|
+
body=CardCreateRequest(
|
|
61
|
+
project_id=self._project.id,
|
|
62
|
+
title=title,
|
|
63
|
+
content=content,
|
|
64
|
+
parent_card_id=parent,
|
|
65
|
+
),
|
|
66
|
+
write=True,
|
|
67
|
+
)
|
|
68
|
+
return self.get(data.card_id)
|
|
69
|
+
|
|
70
|
+
def _clone(self, query: dict[str, Any]) -> Self:
|
|
71
|
+
return type(self)(self._project, query)
|
|
72
|
+
|
|
73
|
+
def filter(self, *, tag: str) -> Self:
|
|
74
|
+
if not isinstance(tag, str) or not tag.strip():
|
|
75
|
+
raise ValueError("tag must be a nonempty string")
|
|
76
|
+
return self._clone({**self._query, "tag": tag})
|
|
77
|
+
|
|
78
|
+
def roots(self) -> Self:
|
|
79
|
+
return self._clone({**self._query, "parent": CardParent.ROOTS})
|
|
80
|
+
|
|
81
|
+
def search(self, query: str) -> CardSearchCollection:
|
|
82
|
+
"""Search this project's cards using the daemon's full-text query syntax.
|
|
83
|
+
|
|
84
|
+
Tag and hierarchy filters cannot be combined with the search endpoint.
|
|
85
|
+
"""
|
|
86
|
+
if not isinstance(query, str):
|
|
87
|
+
raise TypeError("query must be a string")
|
|
88
|
+
if not query.strip():
|
|
89
|
+
raise ValueError("query must be nonempty")
|
|
90
|
+
if self._query:
|
|
91
|
+
raise ValueError("Search does not support tag or hierarchy filters")
|
|
92
|
+
return CardSearchCollection(self._project, query)
|
|
93
|
+
|
|
94
|
+
def _fetch_page(self) -> tuple[list[Card], str | None]:
|
|
95
|
+
data = self._holder._call(
|
|
96
|
+
get_projects_project_id_cards,
|
|
97
|
+
self._project.id,
|
|
98
|
+
limit=200,
|
|
99
|
+
cursor=self._next or UNSET,
|
|
100
|
+
**self._query,
|
|
101
|
+
)
|
|
102
|
+
rows = [Card._from(self._project, item) for item in data.items]
|
|
103
|
+
if any(card.project_id != self._project.id for card in rows):
|
|
104
|
+
raise ProtocolError("Card listing returned an object from another project")
|
|
105
|
+
cursor = data.next_cursor
|
|
106
|
+
if cursor is not None and (
|
|
107
|
+
not isinstance(cursor, str)
|
|
108
|
+
or not cursor
|
|
109
|
+
or not rows
|
|
110
|
+
or cursor == self._next
|
|
111
|
+
):
|
|
112
|
+
raise ProtocolError("Card pagination did not advance")
|
|
113
|
+
return rows, cursor
|
|
114
|
+
|
|
115
|
+
def get(self, card_id: str) -> Card:
|
|
116
|
+
data = self._holder._call(get_cards_card_id, card_id)
|
|
117
|
+
if data.project_id != self._project.id:
|
|
118
|
+
raise NotFoundError(404, "not_found", "Card not found in this project")
|
|
119
|
+
return Card._from(self._project, data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True, eq=False)
|
|
123
|
+
class Card:
|
|
124
|
+
card_id: str
|
|
125
|
+
project_id: str
|
|
126
|
+
title: str
|
|
127
|
+
rel_path: str
|
|
128
|
+
sort_key: float
|
|
129
|
+
created_at: int
|
|
130
|
+
updated_at: int
|
|
131
|
+
parent_card_id: str | None
|
|
132
|
+
deleted_at: int | None
|
|
133
|
+
child_count: int | None
|
|
134
|
+
_project: Project = field(repr=False, compare=False)
|
|
135
|
+
_content: str | None = field(default=None, repr=False, compare=False)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def id(self) -> str:
|
|
139
|
+
return self.card_id
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def content(self) -> str:
|
|
143
|
+
self._project._holder._active()
|
|
144
|
+
if self._content is None:
|
|
145
|
+
self.refresh()
|
|
146
|
+
if self._content is None:
|
|
147
|
+
raise ProtocolError("Card response did not include content")
|
|
148
|
+
return self._content
|
|
149
|
+
|
|
150
|
+
@cached_property
|
|
151
|
+
def children(self) -> CardCollection:
|
|
152
|
+
return CardCollection(self._project, {"parent": self.id})
|
|
153
|
+
|
|
154
|
+
@cached_property
|
|
155
|
+
def tags(self) -> CardTagCollection:
|
|
156
|
+
return CardTagCollection(self)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def _from(cls, project: Project, data: Any) -> Self:
|
|
160
|
+
values: dict[str, Any] = {
|
|
161
|
+
f.name: getattr(data, f.name, None)
|
|
162
|
+
for f in fields(cls)
|
|
163
|
+
if not f.name.startswith("_")
|
|
164
|
+
}
|
|
165
|
+
values = {k: None if isinstance(v, Unset) else v for k, v in values.items()}
|
|
166
|
+
# Collection pages carry content only when asked; an absent field means "not loaded".
|
|
167
|
+
content = getattr(data, "content", None)
|
|
168
|
+
if isinstance(content, Unset):
|
|
169
|
+
content = None
|
|
170
|
+
return cls(**values, _project=project, _content=content)
|
|
171
|
+
|
|
172
|
+
def refresh(self) -> Self:
|
|
173
|
+
fresh = self._project.cards.get(self.id)
|
|
174
|
+
for f in fields(self):
|
|
175
|
+
if f.name != "_project":
|
|
176
|
+
object.__setattr__(self, f.name, getattr(fresh, f.name))
|
|
177
|
+
if "tags" in self.__dict__:
|
|
178
|
+
self.tags.refresh()
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def update(
|
|
182
|
+
self, *, title: str | None | Unset = UNSET, content: str | Unset = UNSET
|
|
183
|
+
) -> Self:
|
|
184
|
+
if isinstance(title, Unset) and isinstance(content, Unset):
|
|
185
|
+
raise TypeError("Supply title or content")
|
|
186
|
+
if not isinstance(title, (str, Unset)) and title is not None:
|
|
187
|
+
raise TypeError("title must be a string or None")
|
|
188
|
+
if not isinstance(content, (str, Unset)):
|
|
189
|
+
raise TypeError("content must be a string")
|
|
190
|
+
# Verify membership and obtain fresh content before any write.
|
|
191
|
+
current = self._project.cards.get(self.id)
|
|
192
|
+
if isinstance(content, Unset):
|
|
193
|
+
content = current.content
|
|
194
|
+
body = CardUpdateRequest(
|
|
195
|
+
updated_at=int(time.time()), title=title, content=content
|
|
196
|
+
)
|
|
197
|
+
self._project._holder._call(patch_cards_card_id, self.id, body=body, write=True)
|
|
198
|
+
return self.refresh()
|
|
199
|
+
|
|
200
|
+
def move(
|
|
201
|
+
self,
|
|
202
|
+
*,
|
|
203
|
+
parent: Card | None | Unset = UNSET,
|
|
204
|
+
before: Card | Unset = UNSET,
|
|
205
|
+
after: Card | Unset = UNSET,
|
|
206
|
+
) -> Self:
|
|
207
|
+
"""Move into a parent or immediately before/after a target card.
|
|
208
|
+
|
|
209
|
+
Supply exactly one Card, or parent=None to append the card to the
|
|
210
|
+
project's top level. Relative moves adopt the target's parent.
|
|
211
|
+
The daemon enforces project membership and prevents hierarchy cycles.
|
|
212
|
+
The moved card is refreshed; evaluated listings need explicit refresh.
|
|
213
|
+
Writes are never retried, even if the subsequent refresh fails.
|
|
214
|
+
"""
|
|
215
|
+
supplied = [
|
|
216
|
+
(intent, target)
|
|
217
|
+
for intent, target in (
|
|
218
|
+
(CardMoveIntent.INTO, parent),
|
|
219
|
+
(CardMoveIntent.BEFORE, before),
|
|
220
|
+
(CardMoveIntent.AFTER, after),
|
|
221
|
+
)
|
|
222
|
+
if not isinstance(target, Unset)
|
|
223
|
+
]
|
|
224
|
+
if len(supplied) != 1:
|
|
225
|
+
raise TypeError("Supply exactly one of parent, before or after")
|
|
226
|
+
intent, target = supplied[0]
|
|
227
|
+
if intent is CardMoveIntent.INTO and target is None:
|
|
228
|
+
# An explicit null parent is the project's top level.
|
|
229
|
+
body = CardMoveRequest(
|
|
230
|
+
project_id=self._project.id,
|
|
231
|
+
intent=CardMoveIntent.TO_END,
|
|
232
|
+
parent_card_id=None,
|
|
233
|
+
)
|
|
234
|
+
elif isinstance(target, Card):
|
|
235
|
+
body = CardMoveRequest(
|
|
236
|
+
project_id=self._project.id,
|
|
237
|
+
intent=intent,
|
|
238
|
+
target_card_id=target.id,
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
raise TypeError("The move target must be a Card")
|
|
242
|
+
self._project._holder._call(
|
|
243
|
+
post_cards_card_id_move, self.id, body=body, write=True
|
|
244
|
+
)
|
|
245
|
+
return self.refresh()
|
|
246
|
+
|
|
247
|
+
def trash(self, *, children: str = "promote") -> None:
|
|
248
|
+
"""Move this card to Trash.
|
|
249
|
+
|
|
250
|
+
With children="promote" (the default), its live children take its place
|
|
251
|
+
among its siblings, keeping their own descendants. With
|
|
252
|
+
children="trash_subtree", every live descendant goes to Trash with it.
|
|
253
|
+
A trashed card cannot be read again until restored, so this object is
|
|
254
|
+
not refreshed. The write is never retried.
|
|
255
|
+
"""
|
|
256
|
+
try:
|
|
257
|
+
policy = DeleteCardsCardIdChildren(children)
|
|
258
|
+
except ValueError:
|
|
259
|
+
raise ValueError('children must be "promote" or "trash_subtree"') from None
|
|
260
|
+
self._project._holder._call(
|
|
261
|
+
delete_cards_card_id, self.id, children=policy, write=True
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def restore(self) -> Self:
|
|
265
|
+
"""Restore this card from Trash, and refresh it.
|
|
266
|
+
|
|
267
|
+
Restoring is single-card: it returns to its saved parent when that
|
|
268
|
+
parent is live, otherwise to its nearest live ancestor or the top level.
|
|
269
|
+
Children promoted when it was trashed stay where they are.
|
|
270
|
+
"""
|
|
271
|
+
self._project._holder._call(post_cards_card_id_restore, self.id, write=True)
|
|
272
|
+
return self.refresh()
|