temperkb-py 0.5.1__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.
Files changed (385) hide show
  1. temper/__init__.py +93 -0
  2. temper/_validate.py +147 -0
  3. temper/act.py +59 -0
  4. temper/client.py +137 -0
  5. temper/connection.py +353 -0
  6. temper/credentials.py +359 -0
  7. temper/errors.py +243 -0
  8. temper/generated/__init__.py +770 -0
  9. temper/generated/api/__init__.py +29 -0
  10. temper/generated/api/access_api.py +1420 -0
  11. temper/generated/api/auditor_api.py +888 -0
  12. temper/generated/api/blobs_api.py +3134 -0
  13. temper/generated/api/cognitive_maps_api.py +3980 -0
  14. temper/generated/api/contexts_api.py +4146 -0
  15. temper/generated/api/data_artifact_shapes_api.py +914 -0
  16. temper/generated/api/data_artifacts_api.py +998 -0
  17. temper/generated/api/events_api.py +607 -0
  18. temper/generated/api/facets_api.py +1627 -0
  19. temper/generated/api/graph_api.py +2506 -0
  20. temper/generated/api/health_api.py +295 -0
  21. temper/generated/api/ingest_api.py +1526 -0
  22. temper/generated/api/invitations_api.py +2036 -0
  23. temper/generated/api/invocations_api.py +1200 -0
  24. temper/generated/api/meta_api.py +629 -0
  25. temper/generated/api/profile_api.py +852 -0
  26. temper/generated/api/query_api.py +337 -0
  27. temper/generated/api/reblocking_api.py +337 -0
  28. temper/generated/api/relationships_api.py +1280 -0
  29. temper/generated/api/resources_api.py +5430 -0
  30. temper/generated/api/schema_api.py +847 -0
  31. temper/generated/api/search_api.py +334 -0
  32. temper/generated/api/slack_link_api.py +598 -0
  33. temper/generated/api/steward_api.py +1463 -0
  34. temper/generated/api/teams_api.py +2692 -0
  35. temper/generated/api_client.py +804 -0
  36. temper/generated/api_response.py +21 -0
  37. temper/generated/configuration.py +606 -0
  38. temper/generated/exceptions.py +218 -0
  39. temper/generated/models/__init__.py +353 -0
  40. temper/generated/models/accept_invitation_response.py +94 -0
  41. temper/generated/models/act_input.py +137 -0
  42. temper/generated/models/act_invocation.py +160 -0
  43. temper/generated/models/act_name.py +262 -0
  44. temper/generated/models/act_quantity.py +96 -0
  45. temper/generated/models/act_refusal.py +94 -0
  46. temper/generated/models/actor_authority.py +38 -0
  47. temper/generated/models/add_member_request.py +92 -0
  48. temper/generated/models/advance_watermark_ack.py +103 -0
  49. temper/generated/models/advance_watermark_request.py +101 -0
  50. temper/generated/models/agent_authorship.py +117 -0
  51. temper/generated/models/anchor_address_resolution.py +151 -0
  52. temper/generated/models/anchor_address_resolution_one_of.py +95 -0
  53. temper/generated/models/anchor_address_resolution_one_of1.py +104 -0
  54. temper/generated/models/anchor_address_resolution_one_of2.py +95 -0
  55. temper/generated/models/anchor_shape.py +114 -0
  56. temper/generated/models/anchor_verdict.py +38 -0
  57. temper/generated/models/append_block_payload.py +110 -0
  58. temper/generated/models/artifact_commit_request.py +163 -0
  59. temper/generated/models/artifact_commit_response.py +95 -0
  60. temper/generated/models/artifact_view.py +119 -0
  61. temper/generated/models/assert_relationship_request.py +154 -0
  62. temper/generated/models/atlas_edge.py +108 -0
  63. temper/generated/models/atlas_entry.py +112 -0
  64. temper/generated/models/atlas_home.py +106 -0
  65. temper/generated/models/atlas_node.py +139 -0
  66. temper/generated/models/atlas_subgraph.py +106 -0
  67. temper/generated/models/audit_citation.py +93 -0
  68. temper/generated/models/audit_sweep_row.py +93 -0
  69. temper/generated/models/auditor_dispatch_tick_request.py +93 -0
  70. temper/generated/models/auditor_dispatch_tick_response.py +104 -0
  71. temper/generated/models/auditor_job_complete_ack.py +96 -0
  72. temper/generated/models/bind_team_outcome.py +93 -0
  73. temper/generated/models/bind_team_request.py +89 -0
  74. temper/generated/models/blob_commit_response.py +104 -0
  75. temper/generated/models/blob_delete_ack.py +91 -0
  76. temper/generated/models/blob_relation_ack.py +89 -0
  77. temper/generated/models/blob_relation_assert_request.py +154 -0
  78. temper/generated/models/blob_relation_direction.py +37 -0
  79. temper/generated/models/blob_relation_edge_direction.py +37 -0
  80. temper/generated/models/blob_relation_row.py +121 -0
  81. temper/generated/models/blob_summary.py +103 -0
  82. temper/generated/models/blob_upload_begin_request.py +93 -0
  83. temper/generated/models/blob_upload_begin_response.py +89 -0
  84. temper/generated/models/blob_upload_finalize_request.py +98 -0
  85. temper/generated/models/blob_upload_progress.py +101 -0
  86. temper/generated/models/blob_upload_segment_info.py +93 -0
  87. temper/generated/models/block_chunk_ref.py +93 -0
  88. temper/generated/models/block_fold_disposition.py +151 -0
  89. temper/generated/models/block_fold_disposition_one_of.py +114 -0
  90. temper/generated/models/block_fold_disposition_one_of1.py +95 -0
  91. temper/generated/models/block_fold_disposition_one_of2.py +95 -0
  92. temper/generated/models/block_provenance_row.py +111 -0
  93. temper/generated/models/block_read.py +151 -0
  94. temper/generated/models/block_read_one_of.py +127 -0
  95. temper/generated/models/block_read_one_of1.py +116 -0
  96. temper/generated/models/block_read_one_of2.py +98 -0
  97. temper/generated/models/block_successor.py +96 -0
  98. temper/generated/models/blocks_response.py +98 -0
  99. temper/generated/models/body_storage.py +37 -0
  100. temper/generated/models/bridge.py +93 -0
  101. temper/generated/models/bulk_reassign_ack.py +89 -0
  102. temper/generated/models/bulk_reassign_request.py +91 -0
  103. temper/generated/models/change_role_request.py +89 -0
  104. temper/generated/models/charter_block.py +92 -0
  105. temper/generated/models/charter_disposition.py +39 -0
  106. temper/generated/models/citation_audit_request.py +104 -0
  107. temper/generated/models/citation_audit_row.py +111 -0
  108. temper/generated/models/claimed_audit_job.py +103 -0
  109. temper/generated/models/claimed_job.py +93 -0
  110. temper/generated/models/close_invocation_request.py +96 -0
  111. temper/generated/models/cogmap_analytics_row.py +105 -0
  112. temper/generated/models/cogmap_detail.py +112 -0
  113. temper/generated/models/cogmap_foundation_row.py +95 -0
  114. temper/generated/models/cogmap_grant_body.py +99 -0
  115. temper/generated/models/cogmap_region_metrics_row.py +126 -0
  116. temper/generated/models/cogmap_region_row.py +109 -0
  117. temper/generated/models/cogmap_regulation_row.py +100 -0
  118. temper/generated/models/cogmap_revoke_body.py +91 -0
  119. temper/generated/models/cogmap_row.py +108 -0
  120. temper/generated/models/cogmap_staleness.py +103 -0
  121. temper/generated/models/combine_node.py +104 -0
  122. temper/generated/models/combine_op.py +38 -0
  123. temper/generated/models/composition.py +103 -0
  124. temper/generated/models/composition_trace.py +96 -0
  125. temper/generated/models/confidence_band.py +38 -0
  126. temper/generated/models/content_response.py +109 -0
  127. temper/generated/models/context_create_request.py +99 -0
  128. temper/generated/models/context_owner_ref.py +154 -0
  129. temper/generated/models/context_owner_ref_one_of.py +88 -0
  130. temper/generated/models/context_owner_ref_one_of1.py +88 -0
  131. temper/generated/models/context_panorama.py +112 -0
  132. temper/generated/models/context_row.py +108 -0
  133. temper/generated/models/context_row_with_counts.py +110 -0
  134. temper/generated/models/create_cogmap_outcome.py +93 -0
  135. temper/generated/models/create_cogmap_request.py +116 -0
  136. temper/generated/models/create_invitation_request.py +91 -0
  137. temper/generated/models/create_request_body.py +102 -0
  138. temper/generated/models/create_review_body.py +93 -0
  139. temper/generated/models/deactivated.py +95 -0
  140. temper/generated/models/delete_response.py +88 -0
  141. temper/generated/models/denied.py +95 -0
  142. temper/generated/models/device_overrides.py +108 -0
  143. temper/generated/models/discouraged_open_meta_key.py +90 -0
  144. temper/generated/models/dispatch_tick_request.py +100 -0
  145. temper/generated/models/dispatch_tick_response.py +104 -0
  146. temper/generated/models/disposition.py +38 -0
  147. temper/generated/models/doc_type_description.py +106 -0
  148. temper/generated/models/doc_type_summary.py +92 -0
  149. temper/generated/models/drift_sweep_row.py +102 -0
  150. temper/generated/models/edge_facet_row.py +142 -0
  151. temper/generated/models/edge_facet_set_request.py +148 -0
  152. temper/generated/models/edge_facets_response.py +99 -0
  153. temper/generated/models/edge_filter.py +102 -0
  154. temper/generated/models/edge_kind.py +39 -0
  155. temper/generated/models/element_event.py +118 -0
  156. temper/generated/models/element_kind.py +37 -0
  157. temper/generated/models/embed_dispatch_summary.py +99 -0
  158. temper/generated/models/enforcement_mode.py +37 -0
  159. temper/generated/models/entitlements.py +106 -0
  160. temper/generated/models/entry_bounds.py +94 -0
  161. temper/generated/models/error_body.py +92 -0
  162. temper/generated/models/error_detail.py +101 -0
  163. temper/generated/models/error_details.py +137 -0
  164. temper/generated/models/event_cursor_response.py +94 -0
  165. temper/generated/models/event_trail.py +102 -0
  166. temper/generated/models/exact_arm.py +106 -0
  167. temper/generated/models/exact_hit.py +94 -0
  168. temper/generated/models/extent.py +151 -0
  169. temper/generated/models/extent_one_of.py +95 -0
  170. temper/generated/models/extent_one_of1.py +95 -0
  171. temper/generated/models/extent_one_of2.py +97 -0
  172. temper/generated/models/facet_ack.py +89 -0
  173. temper/generated/models/facet_predicate.py +90 -0
  174. temper/generated/models/facet_retract_ack.py +89 -0
  175. temper/generated/models/facet_set_request.py +143 -0
  176. temper/generated/models/finalize_payload.py +98 -0
  177. temper/generated/models/fold_relationship_request.py +144 -0
  178. temper/generated/models/grant_outcome.py +88 -0
  179. temper/generated/models/graph_edge_row.py +123 -0
  180. temper/generated/models/group_key_meta.py +92 -0
  181. temper/generated/models/health_response.py +97 -0
  182. temper/generated/models/home_cogmap.py +99 -0
  183. temper/generated/models/home_context.py +105 -0
  184. temper/generated/models/id_kind.py +194 -0
  185. temper/generated/models/id_provenance.py +137 -0
  186. temper/generated/models/id_provenance_one_of.py +98 -0
  187. temper/generated/models/id_provenance_one_of1.py +98 -0
  188. temper/generated/models/id_set.py +107 -0
  189. temper/generated/models/idp_revocation.py +38 -0
  190. temper/generated/models/illegal_transition.py +105 -0
  191. temper/generated/models/ingest_create_response.py +137 -0
  192. temper/generated/models/ingest_delta.py +120 -0
  193. temper/generated/models/ingest_payload.py +224 -0
  194. temper/generated/models/ingest_state.py +37 -0
  195. temper/generated/models/inherited_read_grant.py +93 -0
  196. temper/generated/models/inherited_share.py +91 -0
  197. temper/generated/models/input_source.py +151 -0
  198. temper/generated/models/input_source_one_of.py +108 -0
  199. temper/generated/models/input_source_one_of1.py +95 -0
  200. temper/generated/models/input_source_one_of2.py +95 -0
  201. temper/generated/models/insufficient_authority.py +100 -0
  202. temper/generated/models/intention.py +96 -0
  203. temper/generated/models/invitation_status.py +39 -0
  204. temper/generated/models/invitation_token_request.py +88 -0
  205. temper/generated/models/invitee_invitation.py +112 -0
  206. temper/generated/models/invocation_ack.py +91 -0
  207. temper/generated/models/invocation_act_row.py +114 -0
  208. temper/generated/models/invocation_close_ack.py +92 -0
  209. temper/generated/models/invocation_summary.py +112 -0
  210. temper/generated/models/invocation_view.py +148 -0
  211. temper/generated/models/join_request.py +145 -0
  212. temper/generated/models/join_request_status.py +39 -0
  213. temper/generated/models/kind_owner_input.py +137 -0
  214. temper/generated/models/kind_owner_input_one_of.py +89 -0
  215. temper/generated/models/kind_owner_input_one_of1.py +89 -0
  216. temper/generated/models/lineage_node.py +101 -0
  217. temper/generated/models/managed_meta.py +156 -0
  218. temper/generated/models/match_location.py +103 -0
  219. temper/generated/models/materialize_ack.py +118 -0
  220. temper/generated/models/materialize_delta.py +111 -0
  221. temper/generated/models/materialize_request.py +93 -0
  222. temper/generated/models/merge_policy.py +37 -0
  223. temper/generated/models/meta_update_payload.py +156 -0
  224. temper/generated/models/narrowed_by.py +94 -0
  225. temper/generated/models/no_prior_standing.py +95 -0
  226. temper/generated/models/no_standing.py +95 -0
  227. temper/generated/models/node_home.py +37 -0
  228. temper/generated/models/open_invocation_request.py +98 -0
  229. temper/generated/models/open_meta_convention.py +103 -0
  230. temper/generated/models/ord_op.py +39 -0
  231. temper/generated/models/orphan_node.py +109 -0
  232. temper/generated/models/outcome_declaration.py +97 -0
  233. temper/generated/models/pending_invitation_counts.py +95 -0
  234. temper/generated/models/plan_refusal.py +115 -0
  235. temper/generated/models/plan_refusal_details.py +96 -0
  236. temper/generated/models/polarity.py +37 -0
  237. temper/generated/models/profile.py +126 -0
  238. temper/generated/models/profile_auth_link.py +109 -0
  239. temper/generated/models/profile_update_request.py +111 -0
  240. temper/generated/models/profile_with_entitlements.py +122 -0
  241. temper/generated/models/property_op.py +151 -0
  242. temper/generated/models/property_op_one_of.py +95 -0
  243. temper/generated/models/property_op_one_of1.py +97 -0
  244. temper/generated/models/property_op_one_of2.py +105 -0
  245. temper/generated/models/property_predicate.py +94 -0
  246. temper/generated/models/provenance_source.py +151 -0
  247. temper/generated/models/provenance_source_one_of.py +98 -0
  248. temper/generated/models/provenance_source_one_of1.py +98 -0
  249. temper/generated/models/provenance_source_one_of2.py +97 -0
  250. temper/generated/models/public_system_settings.py +107 -0
  251. temper/generated/models/quantity_scale.py +151 -0
  252. temper/generated/models/quantity_scale_one_of.py +95 -0
  253. temper/generated/models/quantity_scale_one_of1.py +97 -0
  254. temper/generated/models/quantity_scale_one_of2.py +95 -0
  255. temper/generated/models/query_response.py +107 -0
  256. temper/generated/models/reassign_ack.py +91 -0
  257. temper/generated/models/reassign_context_outcome.py +113 -0
  258. temper/generated/models/reassign_context_request.py +89 -0
  259. temper/generated/models/reassign_resource_request.py +89 -0
  260. temper/generated/models/reblock_candidate.py +95 -0
  261. temper/generated/models/reblock_outcome.py +230 -0
  262. temper/generated/models/reblock_outcome_one_of.py +92 -0
  263. temper/generated/models/reblock_outcome_one_of1.py +92 -0
  264. temper/generated/models/reblock_outcome_one_of1_in_progress.py +88 -0
  265. temper/generated/models/reblock_outcome_one_of2.py +92 -0
  266. temper/generated/models/reblock_outcome_one_of3.py +92 -0
  267. temper/generated/models/reblock_outcome_one_of4.py +92 -0
  268. temper/generated/models/reblock_outcome_one_of4_error.py +88 -0
  269. temper/generated/models/reblock_outcome_one_of_reblocked.py +89 -0
  270. temper/generated/models/reblock_receipt.py +114 -0
  271. temper/generated/models/reblock_request.py +109 -0
  272. temper/generated/models/reblock_scope.py +154 -0
  273. temper/generated/models/reblock_scope_one_of.py +89 -0
  274. temper/generated/models/reblock_scope_one_of1.py +89 -0
  275. temper/generated/models/reblock_summary.py +99 -0
  276. temper/generated/models/reconcile_cogmap_request.py +127 -0
  277. temper/generated/models/reconcile_edge.py +102 -0
  278. temper/generated/models/reconcile_edge_tombstone.py +93 -0
  279. temper/generated/models/reconcile_entry.py +116 -0
  280. temper/generated/models/reconcile_outcome.py +98 -0
  281. temper/generated/models/reconcile_telos.py +96 -0
  282. temper/generated/models/reconcile_telos_block.py +90 -0
  283. temper/generated/models/reconcile_tombstone.py +89 -0
  284. temper/generated/models/refusal.py +235 -0
  285. temper/generated/models/refusal_reason.py +670 -0
  286. temper/generated/models/region_disclosure.py +91 -0
  287. temper/generated/models/region_hit.py +98 -0
  288. temper/generated/models/relationship_ack.py +89 -0
  289. temper/generated/models/relationship_target.py +37 -0
  290. temper/generated/models/remove_member_outcome.py +92 -0
  291. temper/generated/models/rename_context_outcome.py +99 -0
  292. temper/generated/models/rename_context_request.py +88 -0
  293. temper/generated/models/requested.py +95 -0
  294. temper/generated/models/residual_bucket.py +90 -0
  295. temper/generated/models/residual_context.py +91 -0
  296. temper/generated/models/residual_groups.py +98 -0
  297. temper/generated/models/residual_owned_reach.py +99 -0
  298. temper/generated/models/resource_annotate_request.py +154 -0
  299. temper/generated/models/resource_create_request.py +152 -0
  300. temper/generated/models/resource_facet_row.py +126 -0
  301. temper/generated/models/resource_facets.py +92 -0
  302. temper/generated/models/resource_facets_response.py +99 -0
  303. temper/generated/models/resource_filter.py +139 -0
  304. temper/generated/models/resource_grant_body.py +99 -0
  305. temper/generated/models/resource_hit.py +119 -0
  306. temper/generated/models/resource_lineage.py +108 -0
  307. temper/generated/models/resource_list_response.py +117 -0
  308. temper/generated/models/resource_revoke_body.py +91 -0
  309. temper/generated/models/resource_section.py +38 -0
  310. temper/generated/models/resource_sort_field.py +42 -0
  311. temper/generated/models/resource_update_request.py +235 -0
  312. temper/generated/models/resource_view.py +202 -0
  313. temper/generated/models/restore_context_outcome.py +97 -0
  314. temper/generated/models/retire_context_outcome.py +95 -0
  315. temper/generated/models/return_spec.py +102 -0
  316. temper/generated/models/retype_relationship_request.py +143 -0
  317. temper/generated/models/revoke_outcome.py +88 -0
  318. temper/generated/models/revoked.py +95 -0
  319. temper/generated/models/reweight_relationship_request.py +139 -0
  320. temper/generated/models/score_kind.py +194 -0
  321. temper/generated/models/scoring.py +94 -0
  322. temper/generated/models/search_params.py +152 -0
  323. temper/generated/models/search_reason.py +38 -0
  324. temper/generated/models/search_response.py +104 -0
  325. temper/generated/models/search_result_row.py +115 -0
  326. temper/generated/models/search_scope.py +38 -0
  327. temper/generated/models/search_scope_info.py +96 -0
  328. temper/generated/models/segment_info.py +91 -0
  329. temper/generated/models/segmented_begin.py +103 -0
  330. temper/generated/models/segmented_begin_response.py +103 -0
  331. temper/generated/models/shape_declare_request.py +160 -0
  332. temper/generated/models/shape_emptiness.py +39 -0
  333. temper/generated/models/shape_view.py +116 -0
  334. temper/generated/models/share_context_outcome.py +93 -0
  335. temper/generated/models/share_context_request.py +89 -0
  336. temper/generated/models/slack_disconnect_request.py +88 -0
  337. temper/generated/models/slack_disconnect_response.py +96 -0
  338. temper/generated/models/slack_disconnected_principal.py +95 -0
  339. temper/generated/models/slice_request.py +95 -0
  340. temper/generated/models/sort_order.py +37 -0
  341. temper/generated/models/stage_disposition.py +39 -0
  342. temper/generated/models/stage_input.py +137 -0
  343. temper/generated/models/stage_input_one_of.py +104 -0
  344. temper/generated/models/stage_input_one_of1.py +111 -0
  345. temper/generated/models/stage_input_trace.py +97 -0
  346. temper/generated/models/stage_node.py +137 -0
  347. temper/generated/models/stage_output.py +137 -0
  348. temper/generated/models/stage_output_one_of.py +105 -0
  349. temper/generated/models/stage_output_one_of1.py +105 -0
  350. temper/generated/models/stage_relation.py +37 -0
  351. temper/generated/models/stage_result.py +162 -0
  352. temper/generated/models/stage_trace.py +163 -0
  353. temper/generated/models/standing.py +40 -0
  354. temper/generated/models/standing_shape.py +103 -0
  355. temper/generated/models/subscription.py +118 -0
  356. temper/generated/models/subscription_override.py +108 -0
  357. temper/generated/models/system_access_details.py +120 -0
  358. temper/generated/models/team_create_request.py +110 -0
  359. temper/generated/models/team_detail.py +121 -0
  360. temper/generated/models/team_invitation.py +115 -0
  361. temper/generated/models/team_member_detail.py +97 -0
  362. temper/generated/models/team_member_row.py +97 -0
  363. temper/generated/models/team_member_source.py +37 -0
  364. temper/generated/models/team_role.py +39 -0
  365. temper/generated/models/team_row.py +111 -0
  366. temper/generated/models/team_update_request.py +100 -0
  367. temper/generated/models/territory.py +117 -0
  368. temper/generated/models/territory_kind.py +38 -0
  369. temper/generated/models/territory_overview.py +116 -0
  370. temper/generated/models/unbind_team_outcome.py +93 -0
  371. temper/generated/models/unrecognized_standing.py +97 -0
  372. temper/generated/models/unshare_context_outcome.py +93 -0
  373. temper/generated/models/vault_config.py +118 -0
  374. temper/generated/models/via_entry.py +106 -0
  375. temper/generated/models/wide_arm.py +108 -0
  376. temper/generated/models/wide_hit.py +94 -0
  377. temper/generated/py.typed +0 -0
  378. temper/generated/rest.py +263 -0
  379. temper/py.typed +0 -0
  380. temper/refs.py +42 -0
  381. temper/version.py +11 -0
  382. temperkb_py-0.5.1.dist-info/METADATA +250 -0
  383. temperkb_py-0.5.1.dist-info/RECORD +385 -0
  384. temperkb_py-0.5.1.dist-info/WHEEL +4 -0
  385. temperkb_py-0.5.1.dist-info/licenses/LICENSE +21 -0
temper/__init__.py ADDED
@@ -0,0 +1,93 @@
1
+ """The Python client for the Temper knowledge base API.
2
+
3
+ Sibling of `temper-rb` and `temper-ts`; the three are pinned to the same wire
4
+ contracts. `temper/generated/` is emitted from the repo-root `openapi.json` — itself
5
+ a product of the Axum router — and is NEVER hand-edited: `cargo make openapi`
6
+ regenerates it, and `openapi-py-drift` (in `cargo make check`, and in CI) fails if the
7
+ committed copy has fallen behind the contract.
8
+
9
+ import temper
10
+ from temper.generated.api.resources_api import ResourcesApi
11
+
12
+ temper.configure(base_url="https://temperkb.io")
13
+ client = temper.Client(temper.ClientCredentials(
14
+ token_url="https://temperkb.io/oauth/token",
15
+ client_id="tmpr_...",
16
+ client_secret="...",
17
+ ))
18
+
19
+ client.whoami()
20
+ client.call(lambda api: ResourcesApi(api).get_resource(uuid), idempotent=True)
21
+ """
22
+
23
+ from temper.act import Act
24
+ from temper.client import MAX_READ_ATTEMPTS, Client, default_backoff
25
+ from temper.connection import (
26
+ SURFACE,
27
+ Connection,
28
+ api_client,
29
+ configure,
30
+ current_connection,
31
+ current_token,
32
+ reset_connection,
33
+ with_token,
34
+ )
35
+ from temper.credentials import BearerToken, ClientCredentials, Credentials
36
+ from temper.errors import (
37
+ BadRequest,
38
+ Conflict,
39
+ Forbidden,
40
+ NotFound,
41
+ PermanentError,
42
+ RateLimited,
43
+ ServerError,
44
+ SystemAccessRequired,
45
+ TemperError,
46
+ TransientError,
47
+ TransportError,
48
+ Unauthorized,
49
+ map_error,
50
+ )
51
+
52
+ # The contract this package was generated against. `generate-temper-py.sh` passes
53
+ # openapi.json's info.version to the generator as `packageVersion`, so the generated
54
+ # tree already carries it — we alias it rather than reasserting it, and callers never
55
+ # reach into `temper.generated` for it. Independent of `__version__`, which is the
56
+ # SDK's own SemVer: a package version and an API version answer different questions.
57
+ from temper.generated import __version__ as CONTRACT_VERSION
58
+ from temper.refs import parse_ref
59
+ from temper.version import __version__
60
+
61
+ __all__ = [
62
+ "CONTRACT_VERSION",
63
+ "MAX_READ_ATTEMPTS",
64
+ "SURFACE",
65
+ "Act",
66
+ "BadRequest",
67
+ "BearerToken",
68
+ "Client",
69
+ "ClientCredentials",
70
+ "Conflict",
71
+ "Connection",
72
+ "Credentials",
73
+ "Forbidden",
74
+ "NotFound",
75
+ "PermanentError",
76
+ "RateLimited",
77
+ "ServerError",
78
+ "SystemAccessRequired",
79
+ "TemperError",
80
+ "TransientError",
81
+ "TransportError",
82
+ "Unauthorized",
83
+ "__version__",
84
+ "api_client",
85
+ "configure",
86
+ "current_connection",
87
+ "current_token",
88
+ "default_backoff",
89
+ "map_error",
90
+ "parse_ref",
91
+ "reset_connection",
92
+ "with_token",
93
+ ]
temper/_validate.py ADDED
@@ -0,0 +1,147 @@
1
+ """What a caller is allowed to hand this package, checked once, at the seam.
2
+
3
+ Three kinds of value arrive from a caller and then get put somewhere that cannot
4
+ defend itself:
5
+
6
+ * An ENDPOINT — `configure(base_url=...)`, `ClientCredentials(token_url=...)`. Every
7
+ API request puts a bearer token on the first; every mint puts the client_secret on
8
+ the second. So the scheme is not cosmetic, and neither is userinfo: `urlsplit` will
9
+ happily accept ``https://id:secret@host/`` and the secret then rides in every error
10
+ message that names the URL.
11
+ * An OPAQUE SECRET or IDENTIFIER — a token, a client id, a device id. These end up in
12
+ an HTTP header or a form field. The failure that actually happens in the field is
13
+ not an attack: it is ``TEMPER_M2M_CLIENT_SECRET=$(cat secret.txt)`` keeping the
14
+ trailing newline, which produces an `invalid_client` no amount of squinting at the
15
+ secret explains. Rejecting it here names it.
16
+ * A plain non-empty string.
17
+
18
+ Checked at CONSTRUCTION, not at first use. `Connection` builds its `ApiClient` lazily,
19
+ so a value validated in `_build()` would surface its error on the first API call —
20
+ several layers and possibly several minutes from the `configure()` that caused it.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import ipaddress
26
+ from urllib.parse import SplitResult, urlsplit
27
+
28
+ #: Hostnames that are the local machine by definition. `.localhost` is reserved for
29
+ #: exactly this by RFC 6761 §6.3, and Docker/CI setups do use `foo.localhost`.
30
+ _LOOPBACK_NAMES = frozenset({"localhost"})
31
+
32
+
33
+ def require_str(value: object, name: str) -> str:
34
+ """A non-empty `str`, and nothing more."""
35
+ if not isinstance(value, str) or not value:
36
+ raise ValueError(f"{name} must be a non-empty str")
37
+ return value
38
+
39
+
40
+ def require_opaque(value: object, name: str) -> str:
41
+ """A non-empty `str` safe to put in a header value or a form field, verbatim.
42
+
43
+ Rejects rather than strips. A stripped value is a guess about what the caller
44
+ meant, and the guess is wrong precisely when it matters: a secret with a stray
45
+ space in the MIDDLE is a different secret, not a formatting slip, and silently
46
+ sending a trimmed one would turn a clear error into a 401 with no cause attached.
47
+
48
+ The whitespace rule is also what keeps a `\\r\\n` out of an `Authorization` or
49
+ `X-Temper-Device-Id` header. urllib3 v2 rejects such a header itself — but it
50
+ does so on the request, wrapped in a transport error, with nothing pointing back
51
+ at the `configure()` or `BearerToken(...)` that introduced it.
52
+ """
53
+ text = require_str(value, name)
54
+ if text != text.strip():
55
+ raise ValueError(
56
+ f"{name} has leading or trailing whitespace; "
57
+ f"strip it at the source (a secret read from a file keeps its newline)"
58
+ )
59
+ if any(ch.isspace() for ch in text):
60
+ raise ValueError(f"{name} must not contain whitespace")
61
+ # Cc/Cf and friends: never part of a real token, and a `\r\n` here is a header
62
+ # split. `str.isprintable()` is false for every control character AND for the
63
+ # separators the whitespace check above already caught.
64
+ if not text.isprintable():
65
+ raise ValueError(f"{name} must not contain control characters")
66
+ return text
67
+
68
+
69
+ def require_endpoint(
70
+ value: object,
71
+ *,
72
+ name: str,
73
+ allow_insecure_http: bool = False,
74
+ ) -> SplitResult:
75
+ """An absolute http(s) origin this package is willing to put a secret on.
76
+
77
+ Returns the parsed URL so a caller can reuse the parts rather than re-splitting.
78
+
79
+ `http://` is refused off the loopback interface: a bearer token and a
80
+ client_secret both travel in the clear over it, to anything on the path. Loopback
81
+ is exempt because that is what a test server and a `temper serve` on your laptop
82
+ are, and `allow_insecure_http=True` is the deliberate opt-out for the case this
83
+ cannot see — a private network where TLS terminates elsewhere. It is a keyword a
84
+ caller has to write, which is the whole point: `verify_ssl=False` used to be a
85
+ typo away.
86
+ """
87
+ text = require_str(value, name)
88
+ # `urlsplit` SILENTLY strips tab, CR and LF anywhere in the URL (CVE-2019-9740's
89
+ # fix). Silently is the problem: a `base_url` with an embedded newline would be
90
+ # accepted here and normalized into something the caller never wrote.
91
+ if not text.isprintable():
92
+ raise ValueError(f"{name} must not contain whitespace or control characters")
93
+
94
+ try:
95
+ parts = urlsplit(text)
96
+ except ValueError as exc: # a malformed IPv6 literal, principally
97
+ raise ValueError(f"{name} is not a parseable URL: {text!r}") from exc
98
+
99
+ if parts.scheme not in ("http", "https") or not parts.netloc:
100
+ raise ValueError(f"{name} must be an absolute http(s) URL, got {text!r}")
101
+
102
+ # `parts.username` is None for `host:port`, so this catches ONLY a real userinfo
103
+ # section. Refused rather than dropped: a caller who wrote credentials into the
104
+ # URL meant them to authenticate something, and quietly discarding them would
105
+ # produce a 401 whose cause is invisible.
106
+ if parts.username is not None or parts.password is not None:
107
+ raise ValueError(
108
+ f"{name} must not carry userinfo (user:password@); "
109
+ f"pass credentials to ClientCredentials or BearerToken instead"
110
+ )
111
+
112
+ try:
113
+ # Accessing it is the check: `port` raises for one out of range or not a number.
114
+ _ = parts.port
115
+ except ValueError as exc:
116
+ raise ValueError(f"{name} has an invalid port: {text!r}") from exc
117
+
118
+ if parts.query or parts.fragment:
119
+ raise ValueError(
120
+ f"{name} must be an origin (optionally with a path prefix), "
121
+ f"not a URL with a query or fragment: {text!r}"
122
+ )
123
+
124
+ if parts.scheme == "http" and not (allow_insecure_http or is_loopback(parts.hostname)):
125
+ raise ValueError(
126
+ f"{name} is plaintext http to a non-loopback host, which would put the "
127
+ f"bearer token and client_secret on the wire in the clear; use https, or "
128
+ f"pass allow_insecure_http=True to accept that deliberately"
129
+ )
130
+
131
+ return parts
132
+
133
+
134
+ def is_loopback(hostname: str | None) -> bool:
135
+ """Whether `hostname` names this machine, by literal address or by reserved name.
136
+
137
+ `urlsplit.hostname` is already lowercased and already has the brackets stripped
138
+ off an IPv6 literal, which is what makes `ip_address` the right test here — and
139
+ it covers the whole 127.0.0.0/8 block, not just 127.0.0.1.
140
+ """
141
+ if not hostname:
142
+ return False
143
+ try:
144
+ return ipaddress.ip_address(hostname).is_loopback
145
+ except ValueError:
146
+ pass
147
+ return hostname in _LOOPBACK_NAMES or hostname.endswith(".localhost")
temper/act.py ADDED
@@ -0,0 +1,59 @@
1
+ """Act context for a write. Optional on every call.
2
+
3
+ The constructor invariant mirrors `ActInput::into_act_context`: Rust's
4
+ `AgentAuthorship.confidence` is non-Option, so authorship without confidence is a 400.
5
+ Rejecting it here is the parse-don't-validate answer — an invalid `Act` cannot be
6
+ constructed, so no call site can send one.
7
+
8
+ `correlation` and `invocation` are exempt: correlation is provenance, never
9
+ authorship, and an act with no supplied correlation self-roots to its own event id.
10
+ Nothing gates on it, so a client may always omit it.
11
+
12
+ Ported from `clients/temper-rb/lib/temper/act.rb`.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ _AUTHORSHIP_FIELDS = ("reasoning", "rationale", "persona", "model")
20
+
21
+
22
+ class Act:
23
+ def __init__(
24
+ self,
25
+ *,
26
+ confidence: Any | None = None,
27
+ reasoning: str | None = None,
28
+ rationale: str | None = None,
29
+ persona: str | None = None,
30
+ model: str | None = None,
31
+ correlation: str | None = None,
32
+ invocation: str | None = None,
33
+ ) -> None:
34
+ authorship = {
35
+ "reasoning": reasoning,
36
+ "rationale": rationale,
37
+ "persona": persona,
38
+ "model": model,
39
+ }
40
+ if confidence is None:
41
+ supplied = [k for k in _AUTHORSHIP_FIELDS if authorship[k] is not None]
42
+ if supplied:
43
+ raise ValueError("Act requires `confidence` when supplying " + ", ".join(supplied))
44
+
45
+ fields: dict[str, Any] = dict(authorship)
46
+ fields["confidence"] = None if confidence is None else str(confidence)
47
+ fields["correlation_id"] = correlation
48
+ fields["invocation_id"] = invocation
49
+ # Nils omitted: the server distinguishes an absent key from null.
50
+ self._fields = {k: v for k, v in fields.items() if v is not None}
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ """The seven `ActInput` wire keys.
54
+
55
+ They flatten into ~30 write bodies and onto the query string of
56
+ `DELETE /api/resources/{id}`, so this is a plain dict rather than a model:
57
+ both destinations take it as keyword arguments.
58
+ """
59
+ return dict(self._fields)
temper/client.py ADDED
@@ -0,0 +1,137 @@
1
+ """The call seam: retry policy, 401 repair, and error translation in one place.
2
+
3
+ A cheap façade over the process-global connection. Holds a credential and nothing
4
+ else; constructing one does no I/O, so a threaded server can build one per user and a
5
+ worker process can memoize one.
6
+
7
+ WHY THERE ARE NO PER-ENDPOINT METHODS HERE. The gem hand-writes `Resources`,
8
+ `Contexts`, `CognitiveMaps` because a Ruby caller otherwise passes an untyped hash.
9
+ The generated Python core already answers that: every operation is a typed method
10
+ taking pydantic models, so a hand-written `resources.create(...)` would be a second,
11
+ worse spelling of something already correct — and a place for the two to drift. This
12
+ is the same call temper-ts makes, for the same reason, in `src/client.ts`.
13
+
14
+ What the generated core does NOT answer is which failures are worth retrying and who
15
+ repairs a dead token. That is what `call()` is.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import time
21
+ from collections.abc import Callable
22
+ from typing import Any, TypeVar
23
+
24
+ import urllib3.exceptions
25
+
26
+ from temper import connection as _connection
27
+ from temper.credentials import Credentials
28
+ from temper.errors import TemperError, TransientError, Unauthorized, map_error
29
+ from temper.generated.api.profile_api import ProfileApi
30
+ from temper.generated.api.search_api import SearchApi
31
+ from temper.generated.api_client import ApiClient
32
+ from temper.generated.exceptions import ApiException
33
+ from temper.generated.models.profile_with_entitlements import ProfileWithEntitlements
34
+ from temper.generated.models.search_params import SearchParams
35
+ from temper.generated.models.search_response import SearchResponse
36
+
37
+ T = TypeVar("T")
38
+
39
+ #: 200ms, 400ms — mirroring MAX_ATTEMPTS and the backoff in
40
+ #: crates/temper-client/src/http.rs, and the gem's Client::DEFAULT_BACKOFF.
41
+ MAX_READ_ATTEMPTS = 3
42
+
43
+
44
+ def default_backoff(attempt: int) -> None:
45
+ time.sleep(0.2 * (2 ** (attempt - 1)))
46
+
47
+
48
+ class Client:
49
+ def __init__(
50
+ self,
51
+ credentials: Credentials,
52
+ *,
53
+ backoff: Callable[[int], None] = default_backoff,
54
+ ) -> None:
55
+ self._credentials = credentials
56
+ self._backoff = backoff
57
+
58
+ def call(self, fn: Callable[[ApiClient], T], *, idempotent: bool = False) -> T:
59
+ """The one seam every call goes through.
60
+
61
+ ``idempotent=True`` — a safe method. 5xx and transport failures retry.
62
+ ``idempotent=False`` — a write. NEVER auto-retried.
63
+
64
+ A 401 is repaired once, for reads and writes alike: re-authenticating is not
65
+ re-submitting. A credential that cannot mint gets its 401 back untouched —
66
+ `BearerToken.refresh()` raises, and raising here would replace temper's real
67
+ answer with a message about the client's own plumbing.
68
+
69
+ Usage::
70
+
71
+ client.call(
72
+ lambda api: ResourcesApi(api).get_resource(resource_id),
73
+ idempotent=True,
74
+ )
75
+ """
76
+ attempt = 0
77
+ reminted = False
78
+ api = _connection.api_client()
79
+
80
+ while True:
81
+ attempt += 1
82
+ try:
83
+ with _connection.with_token(self._credentials.token()):
84
+ return fn(api)
85
+ except (ApiException, urllib3.exceptions.HTTPError) as caught:
86
+ # Bound outside the `except` so the retry/repair decisions below read as
87
+ # straight-line code rather than as branches nested in a handler. The
88
+ # cost is that Python's implicit chaining does not apply out here — hence
89
+ # the explicit `from raw` on the raise, without which the caller's
90
+ # traceback would stop at the mapped error and lose the urllib3 or
91
+ # ApiException frame that says what actually happened.
92
+ error, raw = map_error(caught), caught
93
+
94
+ if self._repair_credentials(error, reminted):
95
+ reminted = True
96
+ continue
97
+
98
+ if not self._retryable_read(error, idempotent, attempt):
99
+ raise error from raw
100
+
101
+ self._backoff(attempt)
102
+
103
+ # -- conveniences the gem also puts on Client itself ----------------------
104
+
105
+ def whoami(self) -> ProfileWithEntitlements:
106
+ """Assert the machine profile resolved, and report what it can reach.
107
+
108
+ Authentication is not authorization. A minted M2M token does not even yield a
109
+ profile on its own: the client_id must already be registered (lookup-or-401 —
110
+ there is no JIT-create branch), and then, without a cogmap write grant and team
111
+ membership, every call authenticates cleanly and 403s. Discovering that here
112
+ beats discovering it on the first write.
113
+ """
114
+ return self.call(lambda api: ProfileApi(api).get_profile(), idempotent=True)
115
+
116
+ def search(self, query: str, **opts: Any) -> SearchResponse:
117
+ """`SearchParams` names the field `query`, not `q`."""
118
+ params = SearchParams(query=query, **opts)
119
+ return self.call(lambda api: SearchApi(api).search(params), idempotent=True)
120
+
121
+ # -- internals -----------------------------------------------------------
122
+
123
+ def _repair_credentials(self, error: TemperError, reminted: bool) -> bool:
124
+ if not isinstance(error, Unauthorized) or reminted:
125
+ return False
126
+ if not self._credentials.can_refresh:
127
+ # The gem reaches the same outcome by letting BearerToken#refresh! raise
128
+ # its own Unauthorized. Asking first is the same decision made where it
129
+ # can be read, and it keeps the SERVER's 401 — body and all — as the
130
+ # error the caller sees.
131
+ return False
132
+ self._credentials.refresh()
133
+ return True
134
+
135
+ @staticmethod
136
+ def _retryable_read(error: TemperError, idempotent: bool, attempt: int) -> bool:
137
+ return idempotent and isinstance(error, TransientError) and attempt < MAX_READ_ATTEMPTS