temperkb-py 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. temperkb_py-0.5.1/.gitignore +10 -0
  2. temperkb_py-0.5.1/LICENSE +21 -0
  3. temperkb_py-0.5.1/PKG-INFO +250 -0
  4. temperkb_py-0.5.1/README.md +232 -0
  5. temperkb_py-0.5.1/pyproject.toml +130 -0
  6. temperkb_py-0.5.1/temper/__init__.py +93 -0
  7. temperkb_py-0.5.1/temper/_validate.py +147 -0
  8. temperkb_py-0.5.1/temper/act.py +59 -0
  9. temperkb_py-0.5.1/temper/client.py +137 -0
  10. temperkb_py-0.5.1/temper/connection.py +353 -0
  11. temperkb_py-0.5.1/temper/credentials.py +359 -0
  12. temperkb_py-0.5.1/temper/errors.py +243 -0
  13. temperkb_py-0.5.1/temper/generated/__init__.py +770 -0
  14. temperkb_py-0.5.1/temper/generated/api/__init__.py +29 -0
  15. temperkb_py-0.5.1/temper/generated/api/access_api.py +1420 -0
  16. temperkb_py-0.5.1/temper/generated/api/auditor_api.py +888 -0
  17. temperkb_py-0.5.1/temper/generated/api/blobs_api.py +3134 -0
  18. temperkb_py-0.5.1/temper/generated/api/cognitive_maps_api.py +3980 -0
  19. temperkb_py-0.5.1/temper/generated/api/contexts_api.py +4146 -0
  20. temperkb_py-0.5.1/temper/generated/api/data_artifact_shapes_api.py +914 -0
  21. temperkb_py-0.5.1/temper/generated/api/data_artifacts_api.py +998 -0
  22. temperkb_py-0.5.1/temper/generated/api/events_api.py +607 -0
  23. temperkb_py-0.5.1/temper/generated/api/facets_api.py +1627 -0
  24. temperkb_py-0.5.1/temper/generated/api/graph_api.py +2506 -0
  25. temperkb_py-0.5.1/temper/generated/api/health_api.py +295 -0
  26. temperkb_py-0.5.1/temper/generated/api/ingest_api.py +1526 -0
  27. temperkb_py-0.5.1/temper/generated/api/invitations_api.py +2036 -0
  28. temperkb_py-0.5.1/temper/generated/api/invocations_api.py +1200 -0
  29. temperkb_py-0.5.1/temper/generated/api/meta_api.py +629 -0
  30. temperkb_py-0.5.1/temper/generated/api/profile_api.py +852 -0
  31. temperkb_py-0.5.1/temper/generated/api/query_api.py +337 -0
  32. temperkb_py-0.5.1/temper/generated/api/reblocking_api.py +337 -0
  33. temperkb_py-0.5.1/temper/generated/api/relationships_api.py +1280 -0
  34. temperkb_py-0.5.1/temper/generated/api/resources_api.py +5430 -0
  35. temperkb_py-0.5.1/temper/generated/api/schema_api.py +847 -0
  36. temperkb_py-0.5.1/temper/generated/api/search_api.py +334 -0
  37. temperkb_py-0.5.1/temper/generated/api/slack_link_api.py +598 -0
  38. temperkb_py-0.5.1/temper/generated/api/steward_api.py +1463 -0
  39. temperkb_py-0.5.1/temper/generated/api/teams_api.py +2692 -0
  40. temperkb_py-0.5.1/temper/generated/api_client.py +804 -0
  41. temperkb_py-0.5.1/temper/generated/api_response.py +21 -0
  42. temperkb_py-0.5.1/temper/generated/configuration.py +606 -0
  43. temperkb_py-0.5.1/temper/generated/exceptions.py +218 -0
  44. temperkb_py-0.5.1/temper/generated/models/__init__.py +353 -0
  45. temperkb_py-0.5.1/temper/generated/models/accept_invitation_response.py +94 -0
  46. temperkb_py-0.5.1/temper/generated/models/act_input.py +137 -0
  47. temperkb_py-0.5.1/temper/generated/models/act_invocation.py +160 -0
  48. temperkb_py-0.5.1/temper/generated/models/act_name.py +262 -0
  49. temperkb_py-0.5.1/temper/generated/models/act_quantity.py +96 -0
  50. temperkb_py-0.5.1/temper/generated/models/act_refusal.py +94 -0
  51. temperkb_py-0.5.1/temper/generated/models/actor_authority.py +38 -0
  52. temperkb_py-0.5.1/temper/generated/models/add_member_request.py +92 -0
  53. temperkb_py-0.5.1/temper/generated/models/advance_watermark_ack.py +103 -0
  54. temperkb_py-0.5.1/temper/generated/models/advance_watermark_request.py +101 -0
  55. temperkb_py-0.5.1/temper/generated/models/agent_authorship.py +117 -0
  56. temperkb_py-0.5.1/temper/generated/models/anchor_address_resolution.py +151 -0
  57. temperkb_py-0.5.1/temper/generated/models/anchor_address_resolution_one_of.py +95 -0
  58. temperkb_py-0.5.1/temper/generated/models/anchor_address_resolution_one_of1.py +104 -0
  59. temperkb_py-0.5.1/temper/generated/models/anchor_address_resolution_one_of2.py +95 -0
  60. temperkb_py-0.5.1/temper/generated/models/anchor_shape.py +114 -0
  61. temperkb_py-0.5.1/temper/generated/models/anchor_verdict.py +38 -0
  62. temperkb_py-0.5.1/temper/generated/models/append_block_payload.py +110 -0
  63. temperkb_py-0.5.1/temper/generated/models/artifact_commit_request.py +163 -0
  64. temperkb_py-0.5.1/temper/generated/models/artifact_commit_response.py +95 -0
  65. temperkb_py-0.5.1/temper/generated/models/artifact_view.py +119 -0
  66. temperkb_py-0.5.1/temper/generated/models/assert_relationship_request.py +154 -0
  67. temperkb_py-0.5.1/temper/generated/models/atlas_edge.py +108 -0
  68. temperkb_py-0.5.1/temper/generated/models/atlas_entry.py +112 -0
  69. temperkb_py-0.5.1/temper/generated/models/atlas_home.py +106 -0
  70. temperkb_py-0.5.1/temper/generated/models/atlas_node.py +139 -0
  71. temperkb_py-0.5.1/temper/generated/models/atlas_subgraph.py +106 -0
  72. temperkb_py-0.5.1/temper/generated/models/audit_citation.py +93 -0
  73. temperkb_py-0.5.1/temper/generated/models/audit_sweep_row.py +93 -0
  74. temperkb_py-0.5.1/temper/generated/models/auditor_dispatch_tick_request.py +93 -0
  75. temperkb_py-0.5.1/temper/generated/models/auditor_dispatch_tick_response.py +104 -0
  76. temperkb_py-0.5.1/temper/generated/models/auditor_job_complete_ack.py +96 -0
  77. temperkb_py-0.5.1/temper/generated/models/bind_team_outcome.py +93 -0
  78. temperkb_py-0.5.1/temper/generated/models/bind_team_request.py +89 -0
  79. temperkb_py-0.5.1/temper/generated/models/blob_commit_response.py +104 -0
  80. temperkb_py-0.5.1/temper/generated/models/blob_delete_ack.py +91 -0
  81. temperkb_py-0.5.1/temper/generated/models/blob_relation_ack.py +89 -0
  82. temperkb_py-0.5.1/temper/generated/models/blob_relation_assert_request.py +154 -0
  83. temperkb_py-0.5.1/temper/generated/models/blob_relation_direction.py +37 -0
  84. temperkb_py-0.5.1/temper/generated/models/blob_relation_edge_direction.py +37 -0
  85. temperkb_py-0.5.1/temper/generated/models/blob_relation_row.py +121 -0
  86. temperkb_py-0.5.1/temper/generated/models/blob_summary.py +103 -0
  87. temperkb_py-0.5.1/temper/generated/models/blob_upload_begin_request.py +93 -0
  88. temperkb_py-0.5.1/temper/generated/models/blob_upload_begin_response.py +89 -0
  89. temperkb_py-0.5.1/temper/generated/models/blob_upload_finalize_request.py +98 -0
  90. temperkb_py-0.5.1/temper/generated/models/blob_upload_progress.py +101 -0
  91. temperkb_py-0.5.1/temper/generated/models/blob_upload_segment_info.py +93 -0
  92. temperkb_py-0.5.1/temper/generated/models/block_chunk_ref.py +93 -0
  93. temperkb_py-0.5.1/temper/generated/models/block_fold_disposition.py +151 -0
  94. temperkb_py-0.5.1/temper/generated/models/block_fold_disposition_one_of.py +114 -0
  95. temperkb_py-0.5.1/temper/generated/models/block_fold_disposition_one_of1.py +95 -0
  96. temperkb_py-0.5.1/temper/generated/models/block_fold_disposition_one_of2.py +95 -0
  97. temperkb_py-0.5.1/temper/generated/models/block_provenance_row.py +111 -0
  98. temperkb_py-0.5.1/temper/generated/models/block_read.py +151 -0
  99. temperkb_py-0.5.1/temper/generated/models/block_read_one_of.py +127 -0
  100. temperkb_py-0.5.1/temper/generated/models/block_read_one_of1.py +116 -0
  101. temperkb_py-0.5.1/temper/generated/models/block_read_one_of2.py +98 -0
  102. temperkb_py-0.5.1/temper/generated/models/block_successor.py +96 -0
  103. temperkb_py-0.5.1/temper/generated/models/blocks_response.py +98 -0
  104. temperkb_py-0.5.1/temper/generated/models/body_storage.py +37 -0
  105. temperkb_py-0.5.1/temper/generated/models/bridge.py +93 -0
  106. temperkb_py-0.5.1/temper/generated/models/bulk_reassign_ack.py +89 -0
  107. temperkb_py-0.5.1/temper/generated/models/bulk_reassign_request.py +91 -0
  108. temperkb_py-0.5.1/temper/generated/models/change_role_request.py +89 -0
  109. temperkb_py-0.5.1/temper/generated/models/charter_block.py +92 -0
  110. temperkb_py-0.5.1/temper/generated/models/charter_disposition.py +39 -0
  111. temperkb_py-0.5.1/temper/generated/models/citation_audit_request.py +104 -0
  112. temperkb_py-0.5.1/temper/generated/models/citation_audit_row.py +111 -0
  113. temperkb_py-0.5.1/temper/generated/models/claimed_audit_job.py +103 -0
  114. temperkb_py-0.5.1/temper/generated/models/claimed_job.py +93 -0
  115. temperkb_py-0.5.1/temper/generated/models/close_invocation_request.py +96 -0
  116. temperkb_py-0.5.1/temper/generated/models/cogmap_analytics_row.py +105 -0
  117. temperkb_py-0.5.1/temper/generated/models/cogmap_detail.py +112 -0
  118. temperkb_py-0.5.1/temper/generated/models/cogmap_foundation_row.py +95 -0
  119. temperkb_py-0.5.1/temper/generated/models/cogmap_grant_body.py +99 -0
  120. temperkb_py-0.5.1/temper/generated/models/cogmap_region_metrics_row.py +126 -0
  121. temperkb_py-0.5.1/temper/generated/models/cogmap_region_row.py +109 -0
  122. temperkb_py-0.5.1/temper/generated/models/cogmap_regulation_row.py +100 -0
  123. temperkb_py-0.5.1/temper/generated/models/cogmap_revoke_body.py +91 -0
  124. temperkb_py-0.5.1/temper/generated/models/cogmap_row.py +108 -0
  125. temperkb_py-0.5.1/temper/generated/models/cogmap_staleness.py +103 -0
  126. temperkb_py-0.5.1/temper/generated/models/combine_node.py +104 -0
  127. temperkb_py-0.5.1/temper/generated/models/combine_op.py +38 -0
  128. temperkb_py-0.5.1/temper/generated/models/composition.py +103 -0
  129. temperkb_py-0.5.1/temper/generated/models/composition_trace.py +96 -0
  130. temperkb_py-0.5.1/temper/generated/models/confidence_band.py +38 -0
  131. temperkb_py-0.5.1/temper/generated/models/content_response.py +109 -0
  132. temperkb_py-0.5.1/temper/generated/models/context_create_request.py +99 -0
  133. temperkb_py-0.5.1/temper/generated/models/context_owner_ref.py +154 -0
  134. temperkb_py-0.5.1/temper/generated/models/context_owner_ref_one_of.py +88 -0
  135. temperkb_py-0.5.1/temper/generated/models/context_owner_ref_one_of1.py +88 -0
  136. temperkb_py-0.5.1/temper/generated/models/context_panorama.py +112 -0
  137. temperkb_py-0.5.1/temper/generated/models/context_row.py +108 -0
  138. temperkb_py-0.5.1/temper/generated/models/context_row_with_counts.py +110 -0
  139. temperkb_py-0.5.1/temper/generated/models/create_cogmap_outcome.py +93 -0
  140. temperkb_py-0.5.1/temper/generated/models/create_cogmap_request.py +116 -0
  141. temperkb_py-0.5.1/temper/generated/models/create_invitation_request.py +91 -0
  142. temperkb_py-0.5.1/temper/generated/models/create_request_body.py +102 -0
  143. temperkb_py-0.5.1/temper/generated/models/create_review_body.py +93 -0
  144. temperkb_py-0.5.1/temper/generated/models/deactivated.py +95 -0
  145. temperkb_py-0.5.1/temper/generated/models/delete_response.py +88 -0
  146. temperkb_py-0.5.1/temper/generated/models/denied.py +95 -0
  147. temperkb_py-0.5.1/temper/generated/models/device_overrides.py +108 -0
  148. temperkb_py-0.5.1/temper/generated/models/discouraged_open_meta_key.py +90 -0
  149. temperkb_py-0.5.1/temper/generated/models/dispatch_tick_request.py +100 -0
  150. temperkb_py-0.5.1/temper/generated/models/dispatch_tick_response.py +104 -0
  151. temperkb_py-0.5.1/temper/generated/models/disposition.py +38 -0
  152. temperkb_py-0.5.1/temper/generated/models/doc_type_description.py +106 -0
  153. temperkb_py-0.5.1/temper/generated/models/doc_type_summary.py +92 -0
  154. temperkb_py-0.5.1/temper/generated/models/drift_sweep_row.py +102 -0
  155. temperkb_py-0.5.1/temper/generated/models/edge_facet_row.py +142 -0
  156. temperkb_py-0.5.1/temper/generated/models/edge_facet_set_request.py +148 -0
  157. temperkb_py-0.5.1/temper/generated/models/edge_facets_response.py +99 -0
  158. temperkb_py-0.5.1/temper/generated/models/edge_filter.py +102 -0
  159. temperkb_py-0.5.1/temper/generated/models/edge_kind.py +39 -0
  160. temperkb_py-0.5.1/temper/generated/models/element_event.py +118 -0
  161. temperkb_py-0.5.1/temper/generated/models/element_kind.py +37 -0
  162. temperkb_py-0.5.1/temper/generated/models/embed_dispatch_summary.py +99 -0
  163. temperkb_py-0.5.1/temper/generated/models/enforcement_mode.py +37 -0
  164. temperkb_py-0.5.1/temper/generated/models/entitlements.py +106 -0
  165. temperkb_py-0.5.1/temper/generated/models/entry_bounds.py +94 -0
  166. temperkb_py-0.5.1/temper/generated/models/error_body.py +92 -0
  167. temperkb_py-0.5.1/temper/generated/models/error_detail.py +101 -0
  168. temperkb_py-0.5.1/temper/generated/models/error_details.py +137 -0
  169. temperkb_py-0.5.1/temper/generated/models/event_cursor_response.py +94 -0
  170. temperkb_py-0.5.1/temper/generated/models/event_trail.py +102 -0
  171. temperkb_py-0.5.1/temper/generated/models/exact_arm.py +106 -0
  172. temperkb_py-0.5.1/temper/generated/models/exact_hit.py +94 -0
  173. temperkb_py-0.5.1/temper/generated/models/extent.py +151 -0
  174. temperkb_py-0.5.1/temper/generated/models/extent_one_of.py +95 -0
  175. temperkb_py-0.5.1/temper/generated/models/extent_one_of1.py +95 -0
  176. temperkb_py-0.5.1/temper/generated/models/extent_one_of2.py +97 -0
  177. temperkb_py-0.5.1/temper/generated/models/facet_ack.py +89 -0
  178. temperkb_py-0.5.1/temper/generated/models/facet_predicate.py +90 -0
  179. temperkb_py-0.5.1/temper/generated/models/facet_retract_ack.py +89 -0
  180. temperkb_py-0.5.1/temper/generated/models/facet_set_request.py +143 -0
  181. temperkb_py-0.5.1/temper/generated/models/finalize_payload.py +98 -0
  182. temperkb_py-0.5.1/temper/generated/models/fold_relationship_request.py +144 -0
  183. temperkb_py-0.5.1/temper/generated/models/grant_outcome.py +88 -0
  184. temperkb_py-0.5.1/temper/generated/models/graph_edge_row.py +123 -0
  185. temperkb_py-0.5.1/temper/generated/models/group_key_meta.py +92 -0
  186. temperkb_py-0.5.1/temper/generated/models/health_response.py +97 -0
  187. temperkb_py-0.5.1/temper/generated/models/home_cogmap.py +99 -0
  188. temperkb_py-0.5.1/temper/generated/models/home_context.py +105 -0
  189. temperkb_py-0.5.1/temper/generated/models/id_kind.py +194 -0
  190. temperkb_py-0.5.1/temper/generated/models/id_provenance.py +137 -0
  191. temperkb_py-0.5.1/temper/generated/models/id_provenance_one_of.py +98 -0
  192. temperkb_py-0.5.1/temper/generated/models/id_provenance_one_of1.py +98 -0
  193. temperkb_py-0.5.1/temper/generated/models/id_set.py +107 -0
  194. temperkb_py-0.5.1/temper/generated/models/idp_revocation.py +38 -0
  195. temperkb_py-0.5.1/temper/generated/models/illegal_transition.py +105 -0
  196. temperkb_py-0.5.1/temper/generated/models/ingest_create_response.py +137 -0
  197. temperkb_py-0.5.1/temper/generated/models/ingest_delta.py +120 -0
  198. temperkb_py-0.5.1/temper/generated/models/ingest_payload.py +224 -0
  199. temperkb_py-0.5.1/temper/generated/models/ingest_state.py +37 -0
  200. temperkb_py-0.5.1/temper/generated/models/inherited_read_grant.py +93 -0
  201. temperkb_py-0.5.1/temper/generated/models/inherited_share.py +91 -0
  202. temperkb_py-0.5.1/temper/generated/models/input_source.py +151 -0
  203. temperkb_py-0.5.1/temper/generated/models/input_source_one_of.py +108 -0
  204. temperkb_py-0.5.1/temper/generated/models/input_source_one_of1.py +95 -0
  205. temperkb_py-0.5.1/temper/generated/models/input_source_one_of2.py +95 -0
  206. temperkb_py-0.5.1/temper/generated/models/insufficient_authority.py +100 -0
  207. temperkb_py-0.5.1/temper/generated/models/intention.py +96 -0
  208. temperkb_py-0.5.1/temper/generated/models/invitation_status.py +39 -0
  209. temperkb_py-0.5.1/temper/generated/models/invitation_token_request.py +88 -0
  210. temperkb_py-0.5.1/temper/generated/models/invitee_invitation.py +112 -0
  211. temperkb_py-0.5.1/temper/generated/models/invocation_ack.py +91 -0
  212. temperkb_py-0.5.1/temper/generated/models/invocation_act_row.py +114 -0
  213. temperkb_py-0.5.1/temper/generated/models/invocation_close_ack.py +92 -0
  214. temperkb_py-0.5.1/temper/generated/models/invocation_summary.py +112 -0
  215. temperkb_py-0.5.1/temper/generated/models/invocation_view.py +148 -0
  216. temperkb_py-0.5.1/temper/generated/models/join_request.py +145 -0
  217. temperkb_py-0.5.1/temper/generated/models/join_request_status.py +39 -0
  218. temperkb_py-0.5.1/temper/generated/models/kind_owner_input.py +137 -0
  219. temperkb_py-0.5.1/temper/generated/models/kind_owner_input_one_of.py +89 -0
  220. temperkb_py-0.5.1/temper/generated/models/kind_owner_input_one_of1.py +89 -0
  221. temperkb_py-0.5.1/temper/generated/models/lineage_node.py +101 -0
  222. temperkb_py-0.5.1/temper/generated/models/managed_meta.py +156 -0
  223. temperkb_py-0.5.1/temper/generated/models/match_location.py +103 -0
  224. temperkb_py-0.5.1/temper/generated/models/materialize_ack.py +118 -0
  225. temperkb_py-0.5.1/temper/generated/models/materialize_delta.py +111 -0
  226. temperkb_py-0.5.1/temper/generated/models/materialize_request.py +93 -0
  227. temperkb_py-0.5.1/temper/generated/models/merge_policy.py +37 -0
  228. temperkb_py-0.5.1/temper/generated/models/meta_update_payload.py +156 -0
  229. temperkb_py-0.5.1/temper/generated/models/narrowed_by.py +94 -0
  230. temperkb_py-0.5.1/temper/generated/models/no_prior_standing.py +95 -0
  231. temperkb_py-0.5.1/temper/generated/models/no_standing.py +95 -0
  232. temperkb_py-0.5.1/temper/generated/models/node_home.py +37 -0
  233. temperkb_py-0.5.1/temper/generated/models/open_invocation_request.py +98 -0
  234. temperkb_py-0.5.1/temper/generated/models/open_meta_convention.py +103 -0
  235. temperkb_py-0.5.1/temper/generated/models/ord_op.py +39 -0
  236. temperkb_py-0.5.1/temper/generated/models/orphan_node.py +109 -0
  237. temperkb_py-0.5.1/temper/generated/models/outcome_declaration.py +97 -0
  238. temperkb_py-0.5.1/temper/generated/models/pending_invitation_counts.py +95 -0
  239. temperkb_py-0.5.1/temper/generated/models/plan_refusal.py +115 -0
  240. temperkb_py-0.5.1/temper/generated/models/plan_refusal_details.py +96 -0
  241. temperkb_py-0.5.1/temper/generated/models/polarity.py +37 -0
  242. temperkb_py-0.5.1/temper/generated/models/profile.py +126 -0
  243. temperkb_py-0.5.1/temper/generated/models/profile_auth_link.py +109 -0
  244. temperkb_py-0.5.1/temper/generated/models/profile_update_request.py +111 -0
  245. temperkb_py-0.5.1/temper/generated/models/profile_with_entitlements.py +122 -0
  246. temperkb_py-0.5.1/temper/generated/models/property_op.py +151 -0
  247. temperkb_py-0.5.1/temper/generated/models/property_op_one_of.py +95 -0
  248. temperkb_py-0.5.1/temper/generated/models/property_op_one_of1.py +97 -0
  249. temperkb_py-0.5.1/temper/generated/models/property_op_one_of2.py +105 -0
  250. temperkb_py-0.5.1/temper/generated/models/property_predicate.py +94 -0
  251. temperkb_py-0.5.1/temper/generated/models/provenance_source.py +151 -0
  252. temperkb_py-0.5.1/temper/generated/models/provenance_source_one_of.py +98 -0
  253. temperkb_py-0.5.1/temper/generated/models/provenance_source_one_of1.py +98 -0
  254. temperkb_py-0.5.1/temper/generated/models/provenance_source_one_of2.py +97 -0
  255. temperkb_py-0.5.1/temper/generated/models/public_system_settings.py +107 -0
  256. temperkb_py-0.5.1/temper/generated/models/quantity_scale.py +151 -0
  257. temperkb_py-0.5.1/temper/generated/models/quantity_scale_one_of.py +95 -0
  258. temperkb_py-0.5.1/temper/generated/models/quantity_scale_one_of1.py +97 -0
  259. temperkb_py-0.5.1/temper/generated/models/quantity_scale_one_of2.py +95 -0
  260. temperkb_py-0.5.1/temper/generated/models/query_response.py +107 -0
  261. temperkb_py-0.5.1/temper/generated/models/reassign_ack.py +91 -0
  262. temperkb_py-0.5.1/temper/generated/models/reassign_context_outcome.py +113 -0
  263. temperkb_py-0.5.1/temper/generated/models/reassign_context_request.py +89 -0
  264. temperkb_py-0.5.1/temper/generated/models/reassign_resource_request.py +89 -0
  265. temperkb_py-0.5.1/temper/generated/models/reblock_candidate.py +95 -0
  266. temperkb_py-0.5.1/temper/generated/models/reblock_outcome.py +230 -0
  267. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of.py +92 -0
  268. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of1.py +92 -0
  269. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of1_in_progress.py +88 -0
  270. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of2.py +92 -0
  271. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of3.py +92 -0
  272. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of4.py +92 -0
  273. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of4_error.py +88 -0
  274. temperkb_py-0.5.1/temper/generated/models/reblock_outcome_one_of_reblocked.py +89 -0
  275. temperkb_py-0.5.1/temper/generated/models/reblock_receipt.py +114 -0
  276. temperkb_py-0.5.1/temper/generated/models/reblock_request.py +109 -0
  277. temperkb_py-0.5.1/temper/generated/models/reblock_scope.py +154 -0
  278. temperkb_py-0.5.1/temper/generated/models/reblock_scope_one_of.py +89 -0
  279. temperkb_py-0.5.1/temper/generated/models/reblock_scope_one_of1.py +89 -0
  280. temperkb_py-0.5.1/temper/generated/models/reblock_summary.py +99 -0
  281. temperkb_py-0.5.1/temper/generated/models/reconcile_cogmap_request.py +127 -0
  282. temperkb_py-0.5.1/temper/generated/models/reconcile_edge.py +102 -0
  283. temperkb_py-0.5.1/temper/generated/models/reconcile_edge_tombstone.py +93 -0
  284. temperkb_py-0.5.1/temper/generated/models/reconcile_entry.py +116 -0
  285. temperkb_py-0.5.1/temper/generated/models/reconcile_outcome.py +98 -0
  286. temperkb_py-0.5.1/temper/generated/models/reconcile_telos.py +96 -0
  287. temperkb_py-0.5.1/temper/generated/models/reconcile_telos_block.py +90 -0
  288. temperkb_py-0.5.1/temper/generated/models/reconcile_tombstone.py +89 -0
  289. temperkb_py-0.5.1/temper/generated/models/refusal.py +235 -0
  290. temperkb_py-0.5.1/temper/generated/models/refusal_reason.py +670 -0
  291. temperkb_py-0.5.1/temper/generated/models/region_disclosure.py +91 -0
  292. temperkb_py-0.5.1/temper/generated/models/region_hit.py +98 -0
  293. temperkb_py-0.5.1/temper/generated/models/relationship_ack.py +89 -0
  294. temperkb_py-0.5.1/temper/generated/models/relationship_target.py +37 -0
  295. temperkb_py-0.5.1/temper/generated/models/remove_member_outcome.py +92 -0
  296. temperkb_py-0.5.1/temper/generated/models/rename_context_outcome.py +99 -0
  297. temperkb_py-0.5.1/temper/generated/models/rename_context_request.py +88 -0
  298. temperkb_py-0.5.1/temper/generated/models/requested.py +95 -0
  299. temperkb_py-0.5.1/temper/generated/models/residual_bucket.py +90 -0
  300. temperkb_py-0.5.1/temper/generated/models/residual_context.py +91 -0
  301. temperkb_py-0.5.1/temper/generated/models/residual_groups.py +98 -0
  302. temperkb_py-0.5.1/temper/generated/models/residual_owned_reach.py +99 -0
  303. temperkb_py-0.5.1/temper/generated/models/resource_annotate_request.py +154 -0
  304. temperkb_py-0.5.1/temper/generated/models/resource_create_request.py +152 -0
  305. temperkb_py-0.5.1/temper/generated/models/resource_facet_row.py +126 -0
  306. temperkb_py-0.5.1/temper/generated/models/resource_facets.py +92 -0
  307. temperkb_py-0.5.1/temper/generated/models/resource_facets_response.py +99 -0
  308. temperkb_py-0.5.1/temper/generated/models/resource_filter.py +139 -0
  309. temperkb_py-0.5.1/temper/generated/models/resource_grant_body.py +99 -0
  310. temperkb_py-0.5.1/temper/generated/models/resource_hit.py +119 -0
  311. temperkb_py-0.5.1/temper/generated/models/resource_lineage.py +108 -0
  312. temperkb_py-0.5.1/temper/generated/models/resource_list_response.py +117 -0
  313. temperkb_py-0.5.1/temper/generated/models/resource_revoke_body.py +91 -0
  314. temperkb_py-0.5.1/temper/generated/models/resource_section.py +38 -0
  315. temperkb_py-0.5.1/temper/generated/models/resource_sort_field.py +42 -0
  316. temperkb_py-0.5.1/temper/generated/models/resource_update_request.py +235 -0
  317. temperkb_py-0.5.1/temper/generated/models/resource_view.py +202 -0
  318. temperkb_py-0.5.1/temper/generated/models/restore_context_outcome.py +97 -0
  319. temperkb_py-0.5.1/temper/generated/models/retire_context_outcome.py +95 -0
  320. temperkb_py-0.5.1/temper/generated/models/return_spec.py +102 -0
  321. temperkb_py-0.5.1/temper/generated/models/retype_relationship_request.py +143 -0
  322. temperkb_py-0.5.1/temper/generated/models/revoke_outcome.py +88 -0
  323. temperkb_py-0.5.1/temper/generated/models/revoked.py +95 -0
  324. temperkb_py-0.5.1/temper/generated/models/reweight_relationship_request.py +139 -0
  325. temperkb_py-0.5.1/temper/generated/models/score_kind.py +194 -0
  326. temperkb_py-0.5.1/temper/generated/models/scoring.py +94 -0
  327. temperkb_py-0.5.1/temper/generated/models/search_params.py +152 -0
  328. temperkb_py-0.5.1/temper/generated/models/search_reason.py +38 -0
  329. temperkb_py-0.5.1/temper/generated/models/search_response.py +104 -0
  330. temperkb_py-0.5.1/temper/generated/models/search_result_row.py +115 -0
  331. temperkb_py-0.5.1/temper/generated/models/search_scope.py +38 -0
  332. temperkb_py-0.5.1/temper/generated/models/search_scope_info.py +96 -0
  333. temperkb_py-0.5.1/temper/generated/models/segment_info.py +91 -0
  334. temperkb_py-0.5.1/temper/generated/models/segmented_begin.py +103 -0
  335. temperkb_py-0.5.1/temper/generated/models/segmented_begin_response.py +103 -0
  336. temperkb_py-0.5.1/temper/generated/models/shape_declare_request.py +160 -0
  337. temperkb_py-0.5.1/temper/generated/models/shape_emptiness.py +39 -0
  338. temperkb_py-0.5.1/temper/generated/models/shape_view.py +116 -0
  339. temperkb_py-0.5.1/temper/generated/models/share_context_outcome.py +93 -0
  340. temperkb_py-0.5.1/temper/generated/models/share_context_request.py +89 -0
  341. temperkb_py-0.5.1/temper/generated/models/slack_disconnect_request.py +88 -0
  342. temperkb_py-0.5.1/temper/generated/models/slack_disconnect_response.py +96 -0
  343. temperkb_py-0.5.1/temper/generated/models/slack_disconnected_principal.py +95 -0
  344. temperkb_py-0.5.1/temper/generated/models/slice_request.py +95 -0
  345. temperkb_py-0.5.1/temper/generated/models/sort_order.py +37 -0
  346. temperkb_py-0.5.1/temper/generated/models/stage_disposition.py +39 -0
  347. temperkb_py-0.5.1/temper/generated/models/stage_input.py +137 -0
  348. temperkb_py-0.5.1/temper/generated/models/stage_input_one_of.py +104 -0
  349. temperkb_py-0.5.1/temper/generated/models/stage_input_one_of1.py +111 -0
  350. temperkb_py-0.5.1/temper/generated/models/stage_input_trace.py +97 -0
  351. temperkb_py-0.5.1/temper/generated/models/stage_node.py +137 -0
  352. temperkb_py-0.5.1/temper/generated/models/stage_output.py +137 -0
  353. temperkb_py-0.5.1/temper/generated/models/stage_output_one_of.py +105 -0
  354. temperkb_py-0.5.1/temper/generated/models/stage_output_one_of1.py +105 -0
  355. temperkb_py-0.5.1/temper/generated/models/stage_relation.py +37 -0
  356. temperkb_py-0.5.1/temper/generated/models/stage_result.py +162 -0
  357. temperkb_py-0.5.1/temper/generated/models/stage_trace.py +163 -0
  358. temperkb_py-0.5.1/temper/generated/models/standing.py +40 -0
  359. temperkb_py-0.5.1/temper/generated/models/standing_shape.py +103 -0
  360. temperkb_py-0.5.1/temper/generated/models/subscription.py +118 -0
  361. temperkb_py-0.5.1/temper/generated/models/subscription_override.py +108 -0
  362. temperkb_py-0.5.1/temper/generated/models/system_access_details.py +120 -0
  363. temperkb_py-0.5.1/temper/generated/models/team_create_request.py +110 -0
  364. temperkb_py-0.5.1/temper/generated/models/team_detail.py +121 -0
  365. temperkb_py-0.5.1/temper/generated/models/team_invitation.py +115 -0
  366. temperkb_py-0.5.1/temper/generated/models/team_member_detail.py +97 -0
  367. temperkb_py-0.5.1/temper/generated/models/team_member_row.py +97 -0
  368. temperkb_py-0.5.1/temper/generated/models/team_member_source.py +37 -0
  369. temperkb_py-0.5.1/temper/generated/models/team_role.py +39 -0
  370. temperkb_py-0.5.1/temper/generated/models/team_row.py +111 -0
  371. temperkb_py-0.5.1/temper/generated/models/team_update_request.py +100 -0
  372. temperkb_py-0.5.1/temper/generated/models/territory.py +117 -0
  373. temperkb_py-0.5.1/temper/generated/models/territory_kind.py +38 -0
  374. temperkb_py-0.5.1/temper/generated/models/territory_overview.py +116 -0
  375. temperkb_py-0.5.1/temper/generated/models/unbind_team_outcome.py +93 -0
  376. temperkb_py-0.5.1/temper/generated/models/unrecognized_standing.py +97 -0
  377. temperkb_py-0.5.1/temper/generated/models/unshare_context_outcome.py +93 -0
  378. temperkb_py-0.5.1/temper/generated/models/vault_config.py +118 -0
  379. temperkb_py-0.5.1/temper/generated/models/via_entry.py +106 -0
  380. temperkb_py-0.5.1/temper/generated/models/wide_arm.py +108 -0
  381. temperkb_py-0.5.1/temper/generated/models/wide_hit.py +94 -0
  382. temperkb_py-0.5.1/temper/generated/py.typed +0 -0
  383. temperkb_py-0.5.1/temper/generated/rest.py +263 -0
  384. temperkb_py-0.5.1/temper/py.typed +0 -0
  385. temperkb_py-0.5.1/temper/refs.py +42 -0
  386. temperkb_py-0.5.1/temper/version.py +11 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ uv.lock
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tasker Systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.5
2
+ Name: temperkb-py
3
+ Version: 0.5.1
4
+ Summary: Python SDK for the Temper knowledge-base API
5
+ Project-URL: Homepage, https://github.com/tasker-systems/temper
6
+ Project-URL: Source, https://github.com/tasker-systems/temper/tree/main/clients/temper-py
7
+ Project-URL: Issues, https://github.com/tasker-systems/temper/issues
8
+ Author-email: Pete Taylor <pete.jc.taylor@hey.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: knowledge-base,openapi,sdk,temper
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: pydantic>=2.11
14
+ Requires-Dist: python-dateutil>=2.8.2
15
+ Requires-Dist: typing-extensions>=4.7.1
16
+ Requires-Dist: urllib3<3.0,>=2.1
17
+ Description-Content-Type: text/markdown
18
+
19
+ # temper-py
20
+
21
+ The Python client for the [Temper](https://github.com/tasker-systems/temper)
22
+ knowledge-base API. Sibling of `temper-rb` and `temper-ts` — all three are pinned to
23
+ the same OpenAPI contract. Workspace-isolated: this package is not a cargo member and
24
+ not a bun workspace member, so nothing in the repo builds it implicitly.
25
+
26
+ ```python
27
+ import temper
28
+ from temper.generated.api.resources_api import ResourcesApi
29
+
30
+ temper.configure(base_url="https://temperkb.io")
31
+
32
+ client = temper.Client(
33
+ temper.ClientCredentials(
34
+ token_url="https://temperkb.io/oauth/token",
35
+ client_id="tmpr_...",
36
+ client_secret="...",
37
+ )
38
+ )
39
+
40
+ client.whoami()
41
+ client.call(lambda api: ResourcesApi(api).get_resource(resource_id), idempotent=True)
42
+ ```
43
+
44
+ ## What is generated and what is not
45
+
46
+ `temper/generated/**` is emitted from the repo-root `openapi.json` — itself a product
47
+ of the Axum router — by a pinned `openapi-generator`. **Never hand-edit it.** A new
48
+ field on a response DTO restales it exactly as it restales `openapi.json` itself, the
49
+ `temper-rb` gem, and `temper-ts`'s `schema.ts`:
50
+
51
+ ```bash
52
+ cargo make openapi # regenerates the spec and all three SDKs
53
+ cargo make openapi-py # just this package
54
+ ```
55
+
56
+ `cargo make check` runs `openapi-py-drift`, which regenerates and diffs. The
57
+ `test-python` CI job runs the same check and never skips, so a contract change that
58
+ leaves this package behind cannot merge.
59
+
60
+ Everything outside `temper/generated/` is hand-written, and
61
+ `.openapi-generator-ignore` keeps the generator off it.
62
+
63
+ | Module | What it is |
64
+ |---|---|
65
+ | `temper.connection` | One `ApiClient` (one urllib3 pool) per process; the token is call-scoped via a `ContextVar` |
66
+ | `temper.credentials` | `BearerToken` and `ClientCredentials`, pinned to `tests/contracts/m2m-token-request.json` |
67
+ | `temper.errors` | The transient/permanent split, and `map_error` |
68
+ | `temper.client` | `Client.call()` — the one seam carrying retry policy and 401 repair |
69
+ | `temper.act` | `ActInput`'s seven wire keys, with the confidence invariant enforced at construction |
70
+ | `temper.refs` | `parse_ref` — a port of `temper_workflow::operations::parse_ref` |
71
+ | `temper._validate` | The admission checks the seams share: endpoints, and values that become headers |
72
+
73
+ ### There are no per-endpoint wrapper methods, deliberately
74
+
75
+ The gem hand-writes `Resources`, `Contexts`, `CognitiveMaps` because a Ruby caller
76
+ otherwise passes an untyped hash. The generated Python core already answers that:
77
+ every operation is a typed method over pydantic models. A hand-written
78
+ `resources.create(...)` would be a second, worse spelling of something already
79
+ correct — and a place for the two to drift. `temper-ts` declines the same wrappers
80
+ for the same reason.
81
+
82
+ What the generated core does *not* answer is which failures are worth retrying and
83
+ who repairs a dead token. That is what `Client.call()` is:
84
+
85
+ ```python
86
+ client.call(fn, idempotent=True) # a safe method: 5xx and transport failures retry
87
+ client.call(fn) # a write: NEVER auto-retried
88
+ ```
89
+
90
+ A 401 is repaired once either way — re-authenticating is not re-submitting. A
91
+ `BearerToken` cannot mint, so its 401 comes back untouched rather than being replaced
92
+ by a message about the client's own plumbing.
93
+
94
+
95
+ ### What `configure()` will pass through
96
+
97
+ Keyword arguments beyond `base_url` and `device_id` reach the generated
98
+ `Configuration`, and only the ones on an **allowlist** do:
99
+
100
+ ```python
101
+ temper.configure(
102
+ base_url="https://temper.internal",
103
+ ssl_ca_cert="/etc/ssl/private-ca.pem", # trust a private CA
104
+ tls_server_name="temper.internal", # the SNI name the cert carries
105
+ connection_pool_maxsize=32,
106
+ proxy="http://proxy.internal:3128",
107
+ )
108
+ ```
109
+
110
+ `ssl_ca_cert`, `ca_cert_data`, `cert_file`, `key_file`, `tls_server_name`,
111
+ `connection_pool_maxsize`, `proxy`, `proxy_headers`, `socket_options`,
112
+ `datetime_format`, `date_format` — that is the whole list, and an unrecognised name
113
+ is a `TypeError` rather than a silent passthrough. Three of the arguments it refuses
114
+ are why the list is an allowlist:
115
+
116
+ | Refused | What it would have done |
117
+ |---|---|
118
+ | `debug=True` | Sets `httplib.HTTPConnection.debuglevel = 1`, a **class** attribute — every HTTP request in the process starts printing its request headers to stdout, `Authorization: Bearer …` included. Raise the level on the `temper.generated` or `urllib3` logger instead; neither touches httplib. |
119
+ | `verify_ssl=False` | `ssl.CERT_NONE` on the pool: any certificate from anything that answers, and the bearer token goes to whoever intercepted the connection. Use `ssl_ca_cert` / `ca_cert_data`. |
120
+ | `assert_hostname=False` | Keeps verification on but stops checking the certificate is for the host you dialled. Use `tls_server_name`. |
121
+
122
+ The rest are refused because this module owns them (`host`, `retries`, the
123
+ `server_*` family) or because credentials are call-scoped, not connection-scoped
124
+ (`access_token`, `api_key`, `username`/`password`). Every refusal names its reason,
125
+ and all of them fire at `configure()` — not at the first API call, which is when a
126
+ lazily-built `Configuration` would have raised.
127
+
128
+ ### Endpoints, and plaintext http
129
+
130
+ `base_url` and `ClientCredentials(token_url=...)` must be absolute `https` URLs, with
131
+ no userinfo (`https://id:secret@host` puts the secret in every error message that
132
+ names the URL) and no query or fragment. Plaintext `http` is accepted for the
133
+ loopback interface — a test server, a `temper serve` on your laptop — and refused
134
+ anywhere else, because a bearer token and a `client_secret` both travel in the clear
135
+ over it. Where TLS genuinely terminates elsewhere, say so:
136
+
137
+ ```python
138
+ temper.configure(base_url="http://temper.internal", allow_insecure_http=True)
139
+ ```
140
+
141
+ ## Connections and forking
142
+
143
+ `temper.configure()` installs a process-global connection: one `ApiClient`, one
144
+ urllib3 pool, one TLS handshake amortized across every call. The access token is *not*
145
+ on it — it is bound per call from the `Client`'s credential, through a `ContextVar`,
146
+ so one connection serves every concurrent caller with their own identity.
147
+
148
+ urllib3 has no fork hook (the gem gets one free from `connection_pool >= 2.4`), so a
149
+ forking server must drop the inherited sockets itself:
150
+
151
+ ```python
152
+ import os, temper
153
+
154
+ os.register_at_fork(after_in_child=temper.reset_connection)
155
+ ```
156
+
157
+ ## Errors
158
+
159
+ `map_error` translates the generated core's `ApiException` — and the raw `urllib3`
160
+ errors a transport failure raises — into a tree whose top-level split is the one that
161
+ matters operationally:
162
+
163
+ - `TransientError` → `ServerError`, `RateLimited` (with `retry_after`), `TransportError`.
164
+ Let these escape a job; a retry is what fixes them.
165
+ - `PermanentError` → `Unauthorized`, `Forbidden`, `SystemAccessRequired`, `NotFound`,
166
+ `Conflict`, `BadRequest`. Catch these and dead-letter them.
167
+
168
+ `SystemAccessRequired.refusal` returns the typed refusal the server sent (`Denied`,
169
+ `Revoked`, `IllegalTransition`, …) so a worker can tell "never granted" from "granted
170
+ and then revoked" without matching on a message string. `refusal_kind` gives the raw
171
+ discriminator when this build predates the kind the server named.
172
+
173
+ > The gem calls the transport failure `Temper::ConnectionError`. Here it is
174
+ > `TransportError`, because `ConnectionError` is a Python builtin and shadowing it
175
+ > would make `except ConnectionError` silently catch the wrong thing.
176
+
177
+ ### Composition bounds raise BEFORE the request `[2026-08-28]`
178
+
179
+ `/api/query`'s contract publishes ceilings on what one composition may declare, and the
180
+ generated pydantic models enforce them locally — so these surface as a `ValidationError`
181
+ at construction (and, since `validate_assignment` is on, at mutation) rather than as a
182
+ `BadRequest` carrying a typed refusal:
183
+
184
+ | field | ceiling |
185
+ |---|---|
186
+ | `Composition.stages` | 64 |
187
+ | `Intention.query` | 4096 |
188
+ | `IdSet.ids` | 256 |
189
+ | `ResourceFilter.doc_type` / `.tags`, `EdgeFilter.labels` | 256 |
190
+
191
+ **This is a behaviour change for code that already builds large plans**: a 300-id `IdSet`
192
+ used to construct fine and reach the server. It now raises before any HTTP call.
193
+
194
+ **The client counts characters; the server counts bytes.** A 4096-character CJK question is
195
+ 8192 bytes — it constructs cleanly here and is refused server-side as `intention_too_long`.
196
+ The skew is one-directional by construction (a UTF-8 string is never fewer bytes than
197
+ characters), so the client can only ever under-enforce, never refuse something the server
198
+ would have run.
199
+
200
+ Two ceilings are deliberately NOT enforced here, because neither is a contract fact: the
201
+ per-stage predicate and probe caps, and the aggregate embed budget
202
+ (`intention_budget_exceeded`) — what a deployment can embed in one request is a property of
203
+ that deployment. Those arrive as refusals.
204
+
205
+
206
+ ## Credentials
207
+
208
+ `BearerToken(token)` and `ClientCredentials(...)` check their inputs at construction,
209
+ because the two ways a credential arrives wrong both surface as an unexplained
210
+ `invalid_client` or 401 hours later:
211
+
212
+ - **Whitespace.** `TEMPER_M2M_CLIENT_SECRET=$(cat secret.txt)` keeps the trailing
213
+ newline. Rejected rather than stripped — a stripped value is a guess, and the same
214
+ guess is wrong for a space in the middle of a secret.
215
+ - **Swapped fields.** A `client_secret` beginning `tmpr_` is a temper *client id* in
216
+ the secret's slot; temper mints secrets as bare base64url.
217
+
218
+ The mint itself is deliberately unadventurous. It does not follow redirects (that
219
+ would re-POST the `client_secret` to whatever origin the `Location` names), it reads
220
+ at most 64 KiB of response, it applies a connect/read timeout — the mint runs under a
221
+ lock, so an issuer that accepts the connection and never answers would otherwise
222
+ block every thread in the process, not one — and it treats a 200 that carries no
223
+ usable `access_token`, `token_type` or `expires_in` as a credential failure rather
224
+ than letting a `KeyError` out of `token()`. A mint that fails drops the cached token
225
+ first, so a caller never goes on presenting one the server has already rejected.
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ uv sync --group dev # honours .python-version
231
+ uv run ruff check . && uv run ruff format --check .
232
+ uv run mypy
233
+ uv run pytest
234
+ uv build # the gemspec-equivalent smoke test
235
+ ```
236
+
237
+ There is no committed lockfile — a library states what it works with rather than
238
+ freezing what its consumers resolve, the same call `temper-rb` makes by gitignoring
239
+ `Gemfile.lock`. What a lockfile would have bought is bought instead by pinning `ruff`
240
+ and `mypy` EXACTLY in `pyproject.toml`: those two can redden CI on their own release
241
+ schedule, and nothing else in the dev group can.
242
+
243
+ Supported interpreters are **3.10+**. That is above the generated package's own `>= 3.9`
244
+ on purpose: 3.9 is end-of-life, and mypy 2.x already requires 3.10, so a 3.9 floor would
245
+ promise a version whose type-check we could not run.
246
+
247
+ Regenerating needs no Python at all — a Rust developer who changed a DTO can run
248
+ `cargo make openapi-py` with either Docker or a JVM. The generator pin and its
249
+ parameters live in one place, `.github/scripts/generate-temper-py.sh`, shared by
250
+ cargo-make and the drift gate.
@@ -0,0 +1,232 @@
1
+ # temper-py
2
+
3
+ The Python client for the [Temper](https://github.com/tasker-systems/temper)
4
+ knowledge-base API. Sibling of `temper-rb` and `temper-ts` — all three are pinned to
5
+ the same OpenAPI contract. Workspace-isolated: this package is not a cargo member and
6
+ not a bun workspace member, so nothing in the repo builds it implicitly.
7
+
8
+ ```python
9
+ import temper
10
+ from temper.generated.api.resources_api import ResourcesApi
11
+
12
+ temper.configure(base_url="https://temperkb.io")
13
+
14
+ client = temper.Client(
15
+ temper.ClientCredentials(
16
+ token_url="https://temperkb.io/oauth/token",
17
+ client_id="tmpr_...",
18
+ client_secret="...",
19
+ )
20
+ )
21
+
22
+ client.whoami()
23
+ client.call(lambda api: ResourcesApi(api).get_resource(resource_id), idempotent=True)
24
+ ```
25
+
26
+ ## What is generated and what is not
27
+
28
+ `temper/generated/**` is emitted from the repo-root `openapi.json` — itself a product
29
+ of the Axum router — by a pinned `openapi-generator`. **Never hand-edit it.** A new
30
+ field on a response DTO restales it exactly as it restales `openapi.json` itself, the
31
+ `temper-rb` gem, and `temper-ts`'s `schema.ts`:
32
+
33
+ ```bash
34
+ cargo make openapi # regenerates the spec and all three SDKs
35
+ cargo make openapi-py # just this package
36
+ ```
37
+
38
+ `cargo make check` runs `openapi-py-drift`, which regenerates and diffs. The
39
+ `test-python` CI job runs the same check and never skips, so a contract change that
40
+ leaves this package behind cannot merge.
41
+
42
+ Everything outside `temper/generated/` is hand-written, and
43
+ `.openapi-generator-ignore` keeps the generator off it.
44
+
45
+ | Module | What it is |
46
+ |---|---|
47
+ | `temper.connection` | One `ApiClient` (one urllib3 pool) per process; the token is call-scoped via a `ContextVar` |
48
+ | `temper.credentials` | `BearerToken` and `ClientCredentials`, pinned to `tests/contracts/m2m-token-request.json` |
49
+ | `temper.errors` | The transient/permanent split, and `map_error` |
50
+ | `temper.client` | `Client.call()` — the one seam carrying retry policy and 401 repair |
51
+ | `temper.act` | `ActInput`'s seven wire keys, with the confidence invariant enforced at construction |
52
+ | `temper.refs` | `parse_ref` — a port of `temper_workflow::operations::parse_ref` |
53
+ | `temper._validate` | The admission checks the seams share: endpoints, and values that become headers |
54
+
55
+ ### There are no per-endpoint wrapper methods, deliberately
56
+
57
+ The gem hand-writes `Resources`, `Contexts`, `CognitiveMaps` because a Ruby caller
58
+ otherwise passes an untyped hash. The generated Python core already answers that:
59
+ every operation is a typed method over pydantic models. A hand-written
60
+ `resources.create(...)` would be a second, worse spelling of something already
61
+ correct — and a place for the two to drift. `temper-ts` declines the same wrappers
62
+ for the same reason.
63
+
64
+ What the generated core does *not* answer is which failures are worth retrying and
65
+ who repairs a dead token. That is what `Client.call()` is:
66
+
67
+ ```python
68
+ client.call(fn, idempotent=True) # a safe method: 5xx and transport failures retry
69
+ client.call(fn) # a write: NEVER auto-retried
70
+ ```
71
+
72
+ A 401 is repaired once either way — re-authenticating is not re-submitting. A
73
+ `BearerToken` cannot mint, so its 401 comes back untouched rather than being replaced
74
+ by a message about the client's own plumbing.
75
+
76
+
77
+ ### What `configure()` will pass through
78
+
79
+ Keyword arguments beyond `base_url` and `device_id` reach the generated
80
+ `Configuration`, and only the ones on an **allowlist** do:
81
+
82
+ ```python
83
+ temper.configure(
84
+ base_url="https://temper.internal",
85
+ ssl_ca_cert="/etc/ssl/private-ca.pem", # trust a private CA
86
+ tls_server_name="temper.internal", # the SNI name the cert carries
87
+ connection_pool_maxsize=32,
88
+ proxy="http://proxy.internal:3128",
89
+ )
90
+ ```
91
+
92
+ `ssl_ca_cert`, `ca_cert_data`, `cert_file`, `key_file`, `tls_server_name`,
93
+ `connection_pool_maxsize`, `proxy`, `proxy_headers`, `socket_options`,
94
+ `datetime_format`, `date_format` — that is the whole list, and an unrecognised name
95
+ is a `TypeError` rather than a silent passthrough. Three of the arguments it refuses
96
+ are why the list is an allowlist:
97
+
98
+ | Refused | What it would have done |
99
+ |---|---|
100
+ | `debug=True` | Sets `httplib.HTTPConnection.debuglevel = 1`, a **class** attribute — every HTTP request in the process starts printing its request headers to stdout, `Authorization: Bearer …` included. Raise the level on the `temper.generated` or `urllib3` logger instead; neither touches httplib. |
101
+ | `verify_ssl=False` | `ssl.CERT_NONE` on the pool: any certificate from anything that answers, and the bearer token goes to whoever intercepted the connection. Use `ssl_ca_cert` / `ca_cert_data`. |
102
+ | `assert_hostname=False` | Keeps verification on but stops checking the certificate is for the host you dialled. Use `tls_server_name`. |
103
+
104
+ The rest are refused because this module owns them (`host`, `retries`, the
105
+ `server_*` family) or because credentials are call-scoped, not connection-scoped
106
+ (`access_token`, `api_key`, `username`/`password`). Every refusal names its reason,
107
+ and all of them fire at `configure()` — not at the first API call, which is when a
108
+ lazily-built `Configuration` would have raised.
109
+
110
+ ### Endpoints, and plaintext http
111
+
112
+ `base_url` and `ClientCredentials(token_url=...)` must be absolute `https` URLs, with
113
+ no userinfo (`https://id:secret@host` puts the secret in every error message that
114
+ names the URL) and no query or fragment. Plaintext `http` is accepted for the
115
+ loopback interface — a test server, a `temper serve` on your laptop — and refused
116
+ anywhere else, because a bearer token and a `client_secret` both travel in the clear
117
+ over it. Where TLS genuinely terminates elsewhere, say so:
118
+
119
+ ```python
120
+ temper.configure(base_url="http://temper.internal", allow_insecure_http=True)
121
+ ```
122
+
123
+ ## Connections and forking
124
+
125
+ `temper.configure()` installs a process-global connection: one `ApiClient`, one
126
+ urllib3 pool, one TLS handshake amortized across every call. The access token is *not*
127
+ on it — it is bound per call from the `Client`'s credential, through a `ContextVar`,
128
+ so one connection serves every concurrent caller with their own identity.
129
+
130
+ urllib3 has no fork hook (the gem gets one free from `connection_pool >= 2.4`), so a
131
+ forking server must drop the inherited sockets itself:
132
+
133
+ ```python
134
+ import os, temper
135
+
136
+ os.register_at_fork(after_in_child=temper.reset_connection)
137
+ ```
138
+
139
+ ## Errors
140
+
141
+ `map_error` translates the generated core's `ApiException` — and the raw `urllib3`
142
+ errors a transport failure raises — into a tree whose top-level split is the one that
143
+ matters operationally:
144
+
145
+ - `TransientError` → `ServerError`, `RateLimited` (with `retry_after`), `TransportError`.
146
+ Let these escape a job; a retry is what fixes them.
147
+ - `PermanentError` → `Unauthorized`, `Forbidden`, `SystemAccessRequired`, `NotFound`,
148
+ `Conflict`, `BadRequest`. Catch these and dead-letter them.
149
+
150
+ `SystemAccessRequired.refusal` returns the typed refusal the server sent (`Denied`,
151
+ `Revoked`, `IllegalTransition`, …) so a worker can tell "never granted" from "granted
152
+ and then revoked" without matching on a message string. `refusal_kind` gives the raw
153
+ discriminator when this build predates the kind the server named.
154
+
155
+ > The gem calls the transport failure `Temper::ConnectionError`. Here it is
156
+ > `TransportError`, because `ConnectionError` is a Python builtin and shadowing it
157
+ > would make `except ConnectionError` silently catch the wrong thing.
158
+
159
+ ### Composition bounds raise BEFORE the request `[2026-08-28]`
160
+
161
+ `/api/query`'s contract publishes ceilings on what one composition may declare, and the
162
+ generated pydantic models enforce them locally — so these surface as a `ValidationError`
163
+ at construction (and, since `validate_assignment` is on, at mutation) rather than as a
164
+ `BadRequest` carrying a typed refusal:
165
+
166
+ | field | ceiling |
167
+ |---|---|
168
+ | `Composition.stages` | 64 |
169
+ | `Intention.query` | 4096 |
170
+ | `IdSet.ids` | 256 |
171
+ | `ResourceFilter.doc_type` / `.tags`, `EdgeFilter.labels` | 256 |
172
+
173
+ **This is a behaviour change for code that already builds large plans**: a 300-id `IdSet`
174
+ used to construct fine and reach the server. It now raises before any HTTP call.
175
+
176
+ **The client counts characters; the server counts bytes.** A 4096-character CJK question is
177
+ 8192 bytes — it constructs cleanly here and is refused server-side as `intention_too_long`.
178
+ The skew is one-directional by construction (a UTF-8 string is never fewer bytes than
179
+ characters), so the client can only ever under-enforce, never refuse something the server
180
+ would have run.
181
+
182
+ Two ceilings are deliberately NOT enforced here, because neither is a contract fact: the
183
+ per-stage predicate and probe caps, and the aggregate embed budget
184
+ (`intention_budget_exceeded`) — what a deployment can embed in one request is a property of
185
+ that deployment. Those arrive as refusals.
186
+
187
+
188
+ ## Credentials
189
+
190
+ `BearerToken(token)` and `ClientCredentials(...)` check their inputs at construction,
191
+ because the two ways a credential arrives wrong both surface as an unexplained
192
+ `invalid_client` or 401 hours later:
193
+
194
+ - **Whitespace.** `TEMPER_M2M_CLIENT_SECRET=$(cat secret.txt)` keeps the trailing
195
+ newline. Rejected rather than stripped — a stripped value is a guess, and the same
196
+ guess is wrong for a space in the middle of a secret.
197
+ - **Swapped fields.** A `client_secret` beginning `tmpr_` is a temper *client id* in
198
+ the secret's slot; temper mints secrets as bare base64url.
199
+
200
+ The mint itself is deliberately unadventurous. It does not follow redirects (that
201
+ would re-POST the `client_secret` to whatever origin the `Location` names), it reads
202
+ at most 64 KiB of response, it applies a connect/read timeout — the mint runs under a
203
+ lock, so an issuer that accepts the connection and never answers would otherwise
204
+ block every thread in the process, not one — and it treats a 200 that carries no
205
+ usable `access_token`, `token_type` or `expires_in` as a credential failure rather
206
+ than letting a `KeyError` out of `token()`. A mint that fails drops the cached token
207
+ first, so a caller never goes on presenting one the server has already rejected.
208
+
209
+ ## Development
210
+
211
+ ```bash
212
+ uv sync --group dev # honours .python-version
213
+ uv run ruff check . && uv run ruff format --check .
214
+ uv run mypy
215
+ uv run pytest
216
+ uv build # the gemspec-equivalent smoke test
217
+ ```
218
+
219
+ There is no committed lockfile — a library states what it works with rather than
220
+ freezing what its consumers resolve, the same call `temper-rb` makes by gitignoring
221
+ `Gemfile.lock`. What a lockfile would have bought is bought instead by pinning `ruff`
222
+ and `mypy` EXACTLY in `pyproject.toml`: those two can redden CI on their own release
223
+ schedule, and nothing else in the dev group can.
224
+
225
+ Supported interpreters are **3.10+**. That is above the generated package's own `>= 3.9`
226
+ on purpose: 3.9 is end-of-life, and mypy 2.x already requires 3.10, so a 3.9 floor would
227
+ promise a version whose type-check we could not run.
228
+
229
+ Regenerating needs no Python at all — a Rust developer who changed a DTO can run
230
+ `cargo make openapi-py` with either Docker or a JVM. The generator pin and its
231
+ parameters live in one place, `.github/scripts/generate-temper-py.sh`, shared by
232
+ cargo-make and the drift gate.
@@ -0,0 +1,130 @@
1
+ # Hand-written, and listed in .openapi-generator-ignore so it stays that way. The
2
+ # generator emits a pyproject of its own (Poetry-flavoured, naming the package
3
+ # `temper.generated`); ours describes the SDK, whose distribution is `temperkb-py`
4
+ # and whose import package is `temper`. PyPI has no scopes, and both natural
5
+ # distribution names were already taken by unrelated projects (temper-py — a TEMPer
6
+ # USB-device reader; temper — an HTML DSL), so the distribution carries the kb.
7
+ # Nothing under temper/ changes with the name: the wheel still installs `temper`.
8
+ [project]
9
+ name = "temperkb-py"
10
+ dynamic = ["version"]
11
+ description = "Python SDK for the Temper knowledge-base API"
12
+ readme = "README.md"
13
+ license = { text = "MIT" }
14
+ authors = [{ name = "Pete Taylor", email = "pete.jc.taylor@hey.com" }]
15
+ keywords = ["temper", "knowledge-base", "openapi", "sdk"]
16
+
17
+ # ABOVE the generated package's own >= 3.9, deliberately. 3.9 reached end of life in
18
+ # October 2025, so promising it would be promising a version that receives no security
19
+ # fixes — and the dev toolchain has already moved: mypy 2.x requires >= 3.10, so a 3.9
20
+ # floor makes the type-check ungettable rather than merely generous. The dev pin in
21
+ # .python-version is newer again, exactly as the gem's >= 3.1 floor sits under a 3.4.10
22
+ # pin: a floor is a promise to callers, a pin is a statement about this checkout.
23
+ requires-python = ">=3.10"
24
+
25
+ # Every one of these is imported by the GENERATED tree, not by the skin — the
26
+ # floors are the generator's own (see the pyproject it emits and we ignore).
27
+ # urllib3 is the `--library=urllib3` transport; the skin reuses it to mint M2M
28
+ # tokens rather than pulling in a second HTTP client for one POST.
29
+ dependencies = [
30
+ "urllib3>=2.1,<3.0",
31
+ "python-dateutil>=2.8.2",
32
+ "pydantic>=2.11",
33
+ "typing-extensions>=4.7.1",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/tasker-systems/temper"
38
+ Source = "https://github.com/tasker-systems/temper/tree/main/clients/temper-py"
39
+ Issues = "https://github.com/tasker-systems/temper/issues"
40
+
41
+ # ruff and mypy are pinned EXACTLY — no caret, no floor — for the reason temper-ts
42
+ # pins openapi-typescript exactly: a linter that moves on its own redden CI on a day
43
+ # when nothing in this repo changed, and the fix is then somebody else's release notes
44
+ # rather than the diff under review. Bump them deliberately, in their own commit.
45
+ # pytest and the stubs get floors: neither can fail a build by gaining an opinion.
46
+ #
47
+ # There is no committed lockfile, matching the gem (whose Gemfile.lock is gitignored)
48
+ # and the library convention it follows: a library's job is to state what it works
49
+ # with, not to freeze what its consumers resolve. The two tools that CAN break a build
50
+ # on their own schedule are pinned above instead, which is the part a lockfile would
51
+ # have been buying.
52
+ [dependency-groups]
53
+ dev = [
54
+ "pytest>=8.0",
55
+ "mypy==2.3.1",
56
+ "ruff==0.16.5",
57
+ "types-python-dateutil>=2.8.19.14",
58
+ ]
59
+
60
+ [build-system]
61
+ requires = ["hatchling"]
62
+ build-backend = "hatchling.build"
63
+
64
+ # Read, not imported. `temper/version.py` must stand alone for the same reason the
65
+ # gem's `lib/temper/version.rb` must: a build that imported `temper` to learn its
66
+ # version would drag in pydantic and urllib3 at build time, and a regression there
67
+ # is invisible to pytest and fatal to `python -m build`. Hatchling regexes the file.
68
+ [tool.hatch.version]
69
+ path = "temper/version.py"
70
+
71
+ [tool.hatch.build.targets.wheel]
72
+ packages = ["temper"]
73
+
74
+ [tool.hatch.build.targets.sdist]
75
+ include = ["temper", "README.md", "LICENSE", "pyproject.toml"]
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # ruff
79
+ # ---------------------------------------------------------------------------
80
+ [tool.ruff]
81
+ target-version = "py310"
82
+ line-length = 100
83
+ # `generate-temper-py.sh` is the only writer of the generated tree. Linting it
84
+ # would mean hand-editing it — the same exclusion, for the same reason, that
85
+ # .rubocop.yml carves out for lib/temper/generated/**.
86
+ extend-exclude = ["temper/generated"]
87
+
88
+ [tool.ruff.lint]
89
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
90
+
91
+ [tool.ruff.lint.per-file-ignores]
92
+ # Asserting on a private module attribute is the point of some of these tests.
93
+ "tests/*" = ["SLF001"]
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # mypy
97
+ # ---------------------------------------------------------------------------
98
+ [tool.mypy]
99
+ # The package's own floor, so the checker sees what the oldest supported interpreter
100
+ # sees.
101
+ python_version = "3.10"
102
+ files = ["temper", "tests"]
103
+ strict = true
104
+ # The generated tree is excluded for the ruff reason above, and re-admitted as an
105
+ # untyped-but-followed import so the skin's own annotations still resolve against
106
+ # real generated classes rather than against Any.
107
+ exclude = ["^temper/generated/"]
108
+
109
+ # `silent`, not `skip`. Skipping makes every generated symbol `Any`, which quietly
110
+ # turns the skin's own annotations into no-ops — `TokenScopedConfiguration` would be
111
+ # subclassing `Any`, and the property it overrides would be checked against nothing.
112
+ # Silent still analyses the tree (so the skin is checked against the REAL generated
113
+ # classes) while suppressing errors inside code no human may edit.
114
+ [[tool.mypy.overrides]]
115
+ module = "temper.generated.*"
116
+ follow_imports = "silent"
117
+ ignore_missing_imports = true
118
+
119
+ # Strict typing is a promise this package makes to CALLERS; a test body annotating
120
+ # every `def test_x() -> None` buys nothing and hides the errors that matter under
121
+ # a hundred that do not. The strict checks that still apply here are the ones about
122
+ # using the package wrongly.
123
+ [[tool.mypy.overrides]]
124
+ module = "tests.*"
125
+ disallow_untyped_defs = false
126
+ disallow_incomplete_defs = false
127
+ disallow_untyped_calls = false
128
+
129
+ [tool.pytest.ini_options]
130
+ testpaths = ["tests"]