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
holder/models/project.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Projects and their synchronous collection queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass, field, fields
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal, Self, cast
|
|
10
|
+
|
|
11
|
+
from ..collections import Collection
|
|
12
|
+
from ..generated.api.default import (
|
|
13
|
+
get_projects,
|
|
14
|
+
get_projects_project_id,
|
|
15
|
+
patch_projects_project_id,
|
|
16
|
+
post_projects,
|
|
17
|
+
post_projects_project_id_repair_hierarchy,
|
|
18
|
+
)
|
|
19
|
+
from ..generated.models import (
|
|
20
|
+
HierarchyRepairRequest,
|
|
21
|
+
ProjectCreateRequest,
|
|
22
|
+
ProjectCreateRequestPrivacyMode,
|
|
23
|
+
ProjectPrivacyMode,
|
|
24
|
+
ProjectSync,
|
|
25
|
+
ProjectUpdateRequest,
|
|
26
|
+
)
|
|
27
|
+
from ..generated.types import UNSET, Unset
|
|
28
|
+
from .card import CardCollection
|
|
29
|
+
from .tag import TagCollection
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from ..client import Holder
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProjectCollection(Collection["Project"]):
|
|
36
|
+
def create(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
name: str,
|
|
40
|
+
root_path: str | os.PathLike[str] | Unset = UNSET,
|
|
41
|
+
privacy_mode: Literal["encrypted_git", "plain"] | Unset = UNSET,
|
|
42
|
+
) -> Project:
|
|
43
|
+
"""Create a project using daemon defaults for omitted settings.
|
|
44
|
+
|
|
45
|
+
Listing filters do not supply creation values. Cached listings remain
|
|
46
|
+
unchanged until refreshed. The write is never retried; if the subsequent
|
|
47
|
+
fetch fails, the project may already exist.
|
|
48
|
+
"""
|
|
49
|
+
if not isinstance(name, str):
|
|
50
|
+
raise TypeError("name must be a string")
|
|
51
|
+
if not isinstance(root_path, Unset):
|
|
52
|
+
root_path = os.fspath(root_path)
|
|
53
|
+
if not isinstance(root_path, str):
|
|
54
|
+
raise TypeError("root_path must be a string or a text path")
|
|
55
|
+
mode = (
|
|
56
|
+
UNSET
|
|
57
|
+
if isinstance(privacy_mode, Unset)
|
|
58
|
+
else ProjectCreateRequestPrivacyMode(privacy_mode)
|
|
59
|
+
)
|
|
60
|
+
data = self._holder._call(
|
|
61
|
+
post_projects,
|
|
62
|
+
body=ProjectCreateRequest(
|
|
63
|
+
name=name, root_path=root_path, privacy_mode=mode
|
|
64
|
+
),
|
|
65
|
+
write=True,
|
|
66
|
+
)
|
|
67
|
+
return self.get(data.project_id)
|
|
68
|
+
|
|
69
|
+
def _clone(self, query: dict[str, Any]) -> Self:
|
|
70
|
+
return type(self)(self._holder, query)
|
|
71
|
+
|
|
72
|
+
def filter(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
name: str | Unset = UNSET,
|
|
76
|
+
updated_after: int | Unset = UNSET,
|
|
77
|
+
updated_before: int | Unset = UNSET,
|
|
78
|
+
) -> Self:
|
|
79
|
+
values = {
|
|
80
|
+
key: value
|
|
81
|
+
for key, value in {
|
|
82
|
+
"name": name,
|
|
83
|
+
"updated_after": updated_after,
|
|
84
|
+
"updated_before": updated_before,
|
|
85
|
+
}.items()
|
|
86
|
+
if not isinstance(value, Unset)
|
|
87
|
+
}
|
|
88
|
+
if "name" in values and not isinstance(name, str):
|
|
89
|
+
raise TypeError("name must be a string")
|
|
90
|
+
for key in ("updated_after", "updated_before"):
|
|
91
|
+
if key in values and (
|
|
92
|
+
not isinstance(values[key], int) or isinstance(values[key], bool)
|
|
93
|
+
):
|
|
94
|
+
raise TypeError(f"{key} must be an integer")
|
|
95
|
+
return self._clone({**self._query, **values})
|
|
96
|
+
|
|
97
|
+
def _fetch_page(self) -> tuple[list[Project], int | None]:
|
|
98
|
+
offset = cast(int, self._next or 0)
|
|
99
|
+
data = self._holder._call(get_projects, limit=100, offset=offset, **self._query)
|
|
100
|
+
rows = [Project._from(self._holder, item) for item in data]
|
|
101
|
+
next_offset = offset + len(data) if len(data) == 100 else None
|
|
102
|
+
return rows, next_offset
|
|
103
|
+
|
|
104
|
+
def get(self, project_id: str) -> Project:
|
|
105
|
+
project = Project._from(
|
|
106
|
+
self._holder, self._holder._call(get_projects_project_id, project_id)
|
|
107
|
+
)
|
|
108
|
+
# get(id) is a direct scoped lookup, not evaluation of a filtered listing.
|
|
109
|
+
return project
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class RepairedCard:
|
|
114
|
+
"""A hidden branch's top card and where repair moves it (None: top level)."""
|
|
115
|
+
|
|
116
|
+
card_id: str
|
|
117
|
+
parent_card_id: str | None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class HierarchyRepair:
|
|
122
|
+
"""Cards hidden under missing or trashed parents, and what repair does."""
|
|
123
|
+
|
|
124
|
+
applied: bool
|
|
125
|
+
moved: list[RepairedCard]
|
|
126
|
+
changed_card_count: int
|
|
127
|
+
cycle_card_ids: list[str]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, eq=False)
|
|
131
|
+
class Project:
|
|
132
|
+
project_id: str
|
|
133
|
+
name: str
|
|
134
|
+
root_path: str
|
|
135
|
+
privacy_mode: ProjectPrivacyMode
|
|
136
|
+
created_at: int
|
|
137
|
+
updated_at: int
|
|
138
|
+
sync: ProjectSync
|
|
139
|
+
git_remote_url: str | None
|
|
140
|
+
git_provider: str | None
|
|
141
|
+
project_key_id: str | None
|
|
142
|
+
card_count: int | None
|
|
143
|
+
root_card_count: int | None
|
|
144
|
+
_holder: Holder = field(repr=False, compare=False)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def id(self) -> str:
|
|
148
|
+
return self.project_id
|
|
149
|
+
|
|
150
|
+
@cached_property
|
|
151
|
+
def cards(self) -> CardCollection:
|
|
152
|
+
return CardCollection(self)
|
|
153
|
+
|
|
154
|
+
@cached_property
|
|
155
|
+
def tags(self) -> TagCollection:
|
|
156
|
+
return TagCollection(self)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def _from(cls, holder: Holder, data: Any) -> Self:
|
|
160
|
+
values: dict[str, Any] = {}
|
|
161
|
+
for f in fields(cls):
|
|
162
|
+
if f.name != "_holder":
|
|
163
|
+
value = getattr(data, f.name, None)
|
|
164
|
+
values[f.name] = None if isinstance(value, Unset) else value
|
|
165
|
+
return cls(**values, _holder=holder)
|
|
166
|
+
|
|
167
|
+
def refresh(self) -> Self:
|
|
168
|
+
fresh = self._holder.projects.get(self.id)
|
|
169
|
+
for f in fields(self):
|
|
170
|
+
if f.name != "_holder":
|
|
171
|
+
object.__setattr__(self, f.name, getattr(fresh, f.name))
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def update(self, *, name: str) -> Self:
|
|
175
|
+
"""Rename this project and refresh its server metadata."""
|
|
176
|
+
if not isinstance(name, str):
|
|
177
|
+
raise TypeError("name must be a string")
|
|
178
|
+
self._holder._call(
|
|
179
|
+
patch_projects_project_id,
|
|
180
|
+
self.id,
|
|
181
|
+
body=ProjectUpdateRequest(updated_at=int(time.time()), name=name),
|
|
182
|
+
write=True,
|
|
183
|
+
)
|
|
184
|
+
return self.refresh()
|
|
185
|
+
|
|
186
|
+
def repair_hierarchy(self, *, apply: bool = False) -> HierarchyRepair:
|
|
187
|
+
"""Find, and with apply=True fix, cards hidden under missing or trashed parents.
|
|
188
|
+
|
|
189
|
+
Each hidden branch moves to its nearest live ancestor (or the top level)
|
|
190
|
+
with its descendants, in one commit. Cards in parent cycles are only
|
|
191
|
+
reported. The daemon also repairs at startup, and every pull repairs
|
|
192
|
+
what it brings in. Cached listings need an explicit refresh.
|
|
193
|
+
"""
|
|
194
|
+
if not isinstance(apply, bool):
|
|
195
|
+
raise TypeError("apply must be a bool")
|
|
196
|
+
data = self._holder._call(
|
|
197
|
+
post_projects_project_id_repair_hierarchy,
|
|
198
|
+
self.id,
|
|
199
|
+
body=HierarchyRepairRequest(apply=apply),
|
|
200
|
+
write=apply,
|
|
201
|
+
)
|
|
202
|
+
return HierarchyRepair(
|
|
203
|
+
applied=data.applied,
|
|
204
|
+
moved=[RepairedCard(m.card_id, m.parent_card_id) for m in data.moved],
|
|
205
|
+
changed_card_count=data.changed_card_count,
|
|
206
|
+
cycle_card_ids=list(data.cycle_card_ids),
|
|
207
|
+
)
|
holder/models/search.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Project-scoped card search results and lazy pagination."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Self, cast
|
|
8
|
+
|
|
9
|
+
from ..collections import Collection
|
|
10
|
+
from ..generated.api.default import get_search_cards
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .card import Card
|
|
14
|
+
from .project import Project
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CardSearchCollection(Collection["CardSearchResult"]):
|
|
18
|
+
"""Lazy relevance-ordered results, with the usual all/refresh/index behavior."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, project: Project, query: str) -> None:
|
|
21
|
+
self._project = project
|
|
22
|
+
super().__init__(project._holder, {"q": query})
|
|
23
|
+
|
|
24
|
+
def _clone(self, query: dict[str, Any]) -> Self:
|
|
25
|
+
return type(self)(self._project, query["q"])
|
|
26
|
+
|
|
27
|
+
def _fetch_page(self) -> tuple[list[CardSearchResult], int | None]:
|
|
28
|
+
offset = cast(int, self._next or 0)
|
|
29
|
+
data = self._holder._call(
|
|
30
|
+
get_search_cards,
|
|
31
|
+
project_id=self._project.id,
|
|
32
|
+
q=self._query["q"],
|
|
33
|
+
limit=100,
|
|
34
|
+
offset=offset,
|
|
35
|
+
)
|
|
36
|
+
rows = [CardSearchResult._from(self._project, item) for item in data]
|
|
37
|
+
return rows, offset + len(rows) if len(rows) == 100 else None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, eq=False)
|
|
41
|
+
class CardSearchResult:
|
|
42
|
+
"""Search metadata from one query, with lazy access to the current card."""
|
|
43
|
+
|
|
44
|
+
card_id: str
|
|
45
|
+
title: str
|
|
46
|
+
created_at: int
|
|
47
|
+
updated_at: int
|
|
48
|
+
snippet: str
|
|
49
|
+
rank: float
|
|
50
|
+
_project: Project = field(repr=False, compare=False)
|
|
51
|
+
|
|
52
|
+
@cached_property
|
|
53
|
+
def card(self) -> Card:
|
|
54
|
+
return self._project.cards.get(self.card_id)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _from(cls, project: Project, data: Any) -> Self:
|
|
58
|
+
return cls(
|
|
59
|
+
card_id=data.card_id,
|
|
60
|
+
title=data.title,
|
|
61
|
+
created_at=data.created_at,
|
|
62
|
+
updated_at=data.updated_at,
|
|
63
|
+
snippet=data.snippet,
|
|
64
|
+
rank=data.rank,
|
|
65
|
+
_project=project,
|
|
66
|
+
)
|
holder/models/tag.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Project-scoped tags and their synchronous collections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
8
|
+
|
|
9
|
+
from ..collections import Collection
|
|
10
|
+
from ..exceptions import NotFoundError, ProtocolError
|
|
11
|
+
from ..generated.api.default import (
|
|
12
|
+
delete_cards_card_id_tags,
|
|
13
|
+
get_cards_card_id,
|
|
14
|
+
get_projects_project_id_tags,
|
|
15
|
+
post_cards_card_id_tags,
|
|
16
|
+
)
|
|
17
|
+
from ..generated.models import CardTagMutationRequest
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .card import Card, CardCollection
|
|
21
|
+
from .project import Project
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TagCollection(Collection["Tag"]):
|
|
25
|
+
"""Lazy project tags, ordered by usage count and then name."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, project: Project) -> None:
|
|
28
|
+
self._project = project
|
|
29
|
+
super().__init__(project._holder)
|
|
30
|
+
|
|
31
|
+
def _clone(self, query: dict[str, Any]) -> Self:
|
|
32
|
+
return type(self)(self._project)
|
|
33
|
+
|
|
34
|
+
def _fetch_page(self) -> tuple[list[Tag], None]:
|
|
35
|
+
data = self._holder._call(get_projects_project_id_tags, self._project.id)
|
|
36
|
+
return [Tag(row.tag, self._project, row.card_count) for row in data], None
|
|
37
|
+
|
|
38
|
+
def get(self, name: str) -> Tag:
|
|
39
|
+
"""Fetch a tag by its normalized name, independently of cached results."""
|
|
40
|
+
if not isinstance(name, str):
|
|
41
|
+
raise TypeError("name must be a string")
|
|
42
|
+
for tag in self.all():
|
|
43
|
+
if tag.name == name:
|
|
44
|
+
return tag
|
|
45
|
+
raise NotFoundError(404, "not_found", "Tag not found in this collection")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CardTagCollection(TagCollection):
|
|
49
|
+
"""Tags attached to one card; mutations use the daemon's editable tag line."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, card: Card) -> None:
|
|
52
|
+
self._card = card
|
|
53
|
+
super().__init__(card._project)
|
|
54
|
+
|
|
55
|
+
def _clone(self, query: dict[str, Any]) -> Self:
|
|
56
|
+
return type(self)(self._card)
|
|
57
|
+
|
|
58
|
+
def _fetch_page(self) -> tuple[list[Tag], None]:
|
|
59
|
+
data = self._holder._call(get_cards_card_id, self._card.id)
|
|
60
|
+
if data.project_id != self._project.id:
|
|
61
|
+
raise NotFoundError(404, "not_found", "Card not found in this project")
|
|
62
|
+
# The generated client retains the daemon's tag array as an additional
|
|
63
|
+
# property because CardData does not yet declare it explicitly.
|
|
64
|
+
names = data.additional_properties.get("tags")
|
|
65
|
+
if not isinstance(names, list) or any(
|
|
66
|
+
not isinstance(name, str) or not name for name in names
|
|
67
|
+
):
|
|
68
|
+
raise ProtocolError("Card response did not include valid tags")
|
|
69
|
+
return [Tag(name, self._project) for name in names], None
|
|
70
|
+
|
|
71
|
+
def add(self, name: str) -> Self:
|
|
72
|
+
"""Add a tag and refresh the card. Validation and normalization are server-side."""
|
|
73
|
+
return self._mutate(post_cards_card_id_tags, name)
|
|
74
|
+
|
|
75
|
+
def remove(self, name: str) -> Self:
|
|
76
|
+
"""Remove a trailing-line tag; body mentions remain until content is edited."""
|
|
77
|
+
return self._mutate(delete_cards_card_id_tags, name)
|
|
78
|
+
|
|
79
|
+
def _mutate(self, endpoint: Any, name: str) -> Self:
|
|
80
|
+
if not isinstance(name, str):
|
|
81
|
+
raise TypeError("name must be a string")
|
|
82
|
+
self._holder._call(
|
|
83
|
+
endpoint,
|
|
84
|
+
self._card.id,
|
|
85
|
+
body=CardTagMutationRequest(project_id=self._project.id, tag=name),
|
|
86
|
+
write=True,
|
|
87
|
+
)
|
|
88
|
+
# Invalidate even if the following read fails. The write is never retried.
|
|
89
|
+
self.refresh()
|
|
90
|
+
self._card.refresh()
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True, eq=False)
|
|
95
|
+
class Tag:
|
|
96
|
+
"""A normalized project tag.
|
|
97
|
+
|
|
98
|
+
Project listings supply card_count; tags read from a card omit it until
|
|
99
|
+
refresh() fetches their project-wide count.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
name: str
|
|
103
|
+
_project: Project = field(repr=False, compare=False)
|
|
104
|
+
card_count: int | None = None
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def project_id(self) -> str:
|
|
108
|
+
return self._project.id
|
|
109
|
+
|
|
110
|
+
@cached_property
|
|
111
|
+
def cards(self) -> CardCollection:
|
|
112
|
+
return self._project.cards.filter(tag=self.name)
|
|
113
|
+
|
|
114
|
+
def refresh(self) -> Self:
|
|
115
|
+
fresh = self._project.tags.get(self.name)
|
|
116
|
+
object.__setattr__(self, "card_count", fresh.card_count)
|
|
117
|
+
return self
|
holder/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: holder-python
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Control your knowledge using Python
|
|
5
|
+
Project-URL: Repository, https://github.com/HolderTeam/holder/tree/main/python/holder
|
|
6
|
+
Project-URL: Issues, https://github.com/HolderTeam/holder/issues
|
|
7
|
+
License-Expression: GPL-3.0-only
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: attrs>=22.2
|
|
10
|
+
Requires-Dist: httpx<0.29,>=0.23.1
|
|
11
|
+
Requires-Dist: typing-extensions>=4.5
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
14
|
+
Requires-Dist: openapi-python-client==0.29.1; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest<10,>=8; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff==0.16.10; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Holder for Python
|
|
20
|
+
|
|
21
|
+
Holder is an open-source, local-first knowledge app for organising your ideas
|
|
22
|
+
and digital information as index cards and the connections between them.
|
|
23
|
+
|
|
24
|
+
Use Python to read and update the projects and cards in your running Holder
|
|
25
|
+
installation. Changes you make in a script appear in Holder too.
|
|
26
|
+
|
|
27
|
+
## Get started
|
|
28
|
+
|
|
29
|
+
1. You need the Holder application on your computer.
|
|
30
|
+
Get it from https://www.holder.team/
|
|
31
|
+
|
|
32
|
+
2. You need Python 3.11 or newer.
|
|
33
|
+
If in doubt, get the latest version (i.e. 3.15) from:
|
|
34
|
+
https://www.python.org/downloads/
|
|
35
|
+
|
|
36
|
+
3. Install the Holder Python library with:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python3 -m pip install holder-python
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Save this as `list_cards.py` and run it with `python3 list_cards.py`:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from holder import Holder
|
|
46
|
+
|
|
47
|
+
with Holder() as holder:
|
|
48
|
+
for project in holder.projects:
|
|
49
|
+
print(project.name)
|
|
50
|
+
for card in project.cards:
|
|
51
|
+
print(" ", card.title)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`Holder()` finds or starts your local Holder daemon using `holderctl`. The
|
|
55
|
+
`with` block closes the Python connection when the script ends; it does not
|
|
56
|
+
stop Holder.
|
|
57
|
+
|
|
58
|
+
## Create and edit cards
|
|
59
|
+
|
|
60
|
+
This example creates a project and two cards. Run it once: each run creates
|
|
61
|
+
another project named `Research`.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from holder import Holder
|
|
65
|
+
|
|
66
|
+
with Holder() as holder:
|
|
67
|
+
project = holder.projects.create(name="Research")
|
|
68
|
+
card = project.cards.create(title="An idea", content="My notes")
|
|
69
|
+
child = card.children.create(title="Next step")
|
|
70
|
+
child.update(content="Try it out")
|
|
71
|
+
card.tags.add("research")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
To work with an existing project, find it by name and get a card by its ID:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from holder import Holder
|
|
78
|
+
|
|
79
|
+
with Holder() as holder:
|
|
80
|
+
matches = holder.projects.filter(name="Research")
|
|
81
|
+
project = matches[0]
|
|
82
|
+
for card in project.cards:
|
|
83
|
+
print(card.id, card.title)
|
|
84
|
+
|
|
85
|
+
# Replace this with an ID printed above.
|
|
86
|
+
card = project.cards.get("card-id-here")
|
|
87
|
+
card.update(title="A better title")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The name filter matches part of a name. If there are no matches, `[0]` raises
|
|
91
|
+
`IndexError`. Card content is available as `card.content`; use `card.update(...)`
|
|
92
|
+
to save edits. Card attributes cannot be assigned directly.
|
|
93
|
+
|
|
94
|
+
## Find cards
|
|
95
|
+
|
|
96
|
+
Search within a project and inspect the matching text:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from holder import Holder
|
|
100
|
+
|
|
101
|
+
with Holder() as holder:
|
|
102
|
+
project = holder.projects.filter(name="Research")[0]
|
|
103
|
+
for result in project.cards.search("research notes"):
|
|
104
|
+
print(result.title, result.snippet)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
You can also list cards with a tag using
|
|
108
|
+
`project.cards.filter(tag="research")`, or list just top-level cards with
|
|
109
|
+
`project.cards.roots()`.
|
|
110
|
+
|
|
111
|
+
## More help
|
|
112
|
+
|
|
113
|
+
- [More scripting tasks](https://github.com/HolderTeam/holder/blob/827907ae32927e7c80fb9fe077bd1dc513f61763/python/holder/docs/scripting.md): moves, Trash, tags, collection
|
|
114
|
+
refreshes, and connecting to a specific daemon.
|
|
115
|
+
- [Low-level client](https://github.com/HolderTeam/holder/blob/827907ae32927e7c80fb9fe077bd1dc513f61763/python/holder/docs/low-level-client.md): direct access to HTTP endpoints.
|
|
116
|
+
- [Development](https://github.com/HolderTeam/holder/blob/827907ae32927e7c80fb9fe077bd1dc513f61763/python/holder/docs/development.md): tests, builds, and generated code.
|
|
117
|
+
- [Publishing](https://github.com/HolderTeam/holder/blob/827907ae32927e7c80fb9fe077bd1dc513f61763/python/holder/docs/publishing.md): PyPI setup and release steps.
|
|
118
|
+
|
|
119
|
+
For offline analysis and bulk work without a running Holder installation, see
|
|
120
|
+
[Holder Kit](https://github.com/HolderTeam/holder/tree/827907ae32927e7c80fb9fe077bd1dc513f61763/python/kit).
|