docket-client 0.3.2__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 (199) hide show
  1. docket_client-0.3.2/.gitignore +26 -0
  2. docket_client-0.3.2/PKG-INFO +233 -0
  3. docket_client-0.3.2/README.md +202 -0
  4. docket_client-0.3.2/docket/__init__.py +8 -0
  5. docket_client-0.3.2/docket/api/__init__.py +1 -0
  6. docket_client-0.3.2/docket/api/access/__init__.py +1 -0
  7. docket_client-0.3.2/docket/api/access/add_principal.py +193 -0
  8. docket_client-0.3.2/docket/api/access/archive_role_assignment.py +182 -0
  9. docket_client-0.3.2/docket/api/access/create_api_token.py +198 -0
  10. docket_client-0.3.2/docket/api/access/create_application_principal.py +188 -0
  11. docket_client-0.3.2/docket/api/access/create_role_assignment.py +194 -0
  12. docket_client-0.3.2/docket/api/access/list_api_tokens.py +184 -0
  13. docket_client-0.3.2/docket/api/access/list_principals.py +184 -0
  14. docket_client-0.3.2/docket/api/access/list_role_assignments.py +200 -0
  15. docket_client-0.3.2/docket/api/access/revoke_api_token.py +173 -0
  16. docket_client-0.3.2/docket/api/identity/__init__.py +1 -0
  17. docket_client-0.3.2/docket/api/identity/create_my_api_token.py +206 -0
  18. docket_client-0.3.2/docket/api/identity/get_me.py +127 -0
  19. docket_client-0.3.2/docket/api/identity/list_my_api_tokens.py +151 -0
  20. docket_client-0.3.2/docket/api/identity/revoke_my_api_token.py +178 -0
  21. docket_client-0.3.2/docket/api/recurring_acquisitions/__init__.py +1 -0
  22. docket_client-0.3.2/docket/api/recurring_acquisitions/archive_recurring_acquisition.py +207 -0
  23. docket_client-0.3.2/docket/api/recurring_acquisitions/create_recurring_acquisition.py +218 -0
  24. docket_client-0.3.2/docket/api/recurring_acquisitions/get_recurring_acquisition.py +163 -0
  25. docket_client-0.3.2/docket/api/recurring_acquisitions/list_recurring_acquisitions.py +248 -0
  26. docket_client-0.3.2/docket/api/recurring_acquisitions/preview_recurring_acquisition.py +187 -0
  27. docket_client-0.3.2/docket/api/recurring_acquisitions/preview_split_recurring_acquisition.py +232 -0
  28. docket_client-0.3.2/docket/api/recurring_acquisitions/split_recurring_acquisition.py +258 -0
  29. docket_client-0.3.2/docket/api/runs/__init__.py +1 -0
  30. docket_client-0.3.2/docket/api/runs/get_run.py +160 -0
  31. docket_client-0.3.2/docket/api/runs/list_runs.py +180 -0
  32. docket_client-0.3.2/docket/api/runs/report_run_outcome.py +206 -0
  33. docket_client-0.3.2/docket/api/scheduled_acquisitions/__init__.py +1 -0
  34. docket_client-0.3.2/docket/api/scheduled_acquisitions/archive_scheduled_acquisition.py +203 -0
  35. docket_client-0.3.2/docket/api/scheduled_acquisitions/create_scheduled_acquisition.py +185 -0
  36. docket_client-0.3.2/docket/api/scheduled_acquisitions/get_resolved_settings.py +169 -0
  37. docket_client-0.3.2/docket/api/scheduled_acquisitions/get_scheduled_acquisition.py +159 -0
  38. docket_client-0.3.2/docket/api/scheduled_acquisitions/list_active_acquisitions.py +297 -0
  39. docket_client-0.3.2/docket/api/scheduled_acquisitions/list_runs_for_scheduled_acquisition.py +191 -0
  40. docket_client-0.3.2/docket/api/scheduled_acquisitions/list_scheduled_acquisitions.py +332 -0
  41. docket_client-0.3.2/docket/api/scheduled_acquisitions/preview_scheduled_acquisition_edit.py +224 -0
  42. docket_client-0.3.2/docket/api/scheduled_acquisitions/set_planning_status.py +199 -0
  43. docket_client-0.3.2/docket/api/scheduled_acquisitions/start_run.py +221 -0
  44. docket_client-0.3.2/docket/api/scheduled_acquisitions/update_scheduled_acquisition.py +226 -0
  45. docket_client-0.3.2/docket/api/settings/__init__.py +1 -0
  46. docket_client-0.3.2/docket/api/settings/list_behavior_curriculum_discriminator_values.py +188 -0
  47. docket_client-0.3.2/docket/api/settings/list_behavior_curriculum_settings.py +199 -0
  48. docket_client-0.3.2/docket/api/settings/list_fip_discriminator_values.py +184 -0
  49. docket_client-0.3.2/docket/api/settings/list_fip_settings.py +195 -0
  50. docket_client-0.3.2/docket/api/settings/list_live_behavior_curriculum_settings.py +233 -0
  51. docket_client-0.3.2/docket/api/settings/list_live_fip_settings.py +225 -0
  52. docket_client-0.3.2/docket/api/settings/preview_behavior_curriculum_settings.py +191 -0
  53. docket_client-0.3.2/docket/api/settings/preview_fip_settings.py +177 -0
  54. docket_client-0.3.2/docket/api/settings/push_behavior_curriculum_settings.py +222 -0
  55. docket_client-0.3.2/docket/api/settings/push_fip_settings.py +206 -0
  56. docket_client-0.3.2/docket/api/templates/__init__.py +1 -0
  57. docket_client-0.3.2/docket/api/templates/archive_template.py +203 -0
  58. docket_client-0.3.2/docket/api/templates/create_template.py +206 -0
  59. docket_client-0.3.2/docket/api/templates/get_template.py +159 -0
  60. docket_client-0.3.2/docket/api/templates/list_templates.py +174 -0
  61. docket_client-0.3.2/docket/api/templates/update_template.py +234 -0
  62. docket_client-0.3.2/docket/api/vocabulary/__init__.py +1 -0
  63. docket_client-0.3.2/docket/api/vocabulary/list_instruments.py +165 -0
  64. docket_client-0.3.2/docket/api/vocabulary/list_persons.py +132 -0
  65. docket_client-0.3.2/docket/api/vocabulary/list_projects.py +132 -0
  66. docket_client-0.3.2/docket/api/vocabulary/list_subjects.py +132 -0
  67. docket_client-0.3.2/docket/api/vocabulary/lookup_person.py +166 -0
  68. docket_client-0.3.2/docket/api/vocabulary/lookup_subject.py +166 -0
  69. docket_client-0.3.2/docket/api/vocabulary/sync_vocabulary.py +148 -0
  70. docket_client-0.3.2/docket/client.py +272 -0
  71. docket_client-0.3.2/docket/errors.py +16 -0
  72. docket_client-0.3.2/docket/models/__init__.py +221 -0
  73. docket_client-0.3.2/docket/models/access_role.py +12 -0
  74. docket_client-0.3.2/docket/models/access_role_nullable_type_1.py +12 -0
  75. docket_client-0.3.2/docket/models/access_role_nullable_type_2_type_1.py +12 -0
  76. docket_client-0.3.2/docket/models/access_role_nullable_type_3_type_1.py +12 -0
  77. docket_client-0.3.2/docket/models/acquisition_variable.py +72 -0
  78. docket_client-0.3.2/docket/models/acquisition_warning.py +126 -0
  79. docket_client-0.3.2/docket/models/add_principal_request.py +83 -0
  80. docket_client-0.3.2/docket/models/api_token_response.py +271 -0
  81. docket_client-0.3.2/docket/models/api_token_status.py +10 -0
  82. docket_client-0.3.2/docket/models/archive_recurring_acquisition_request.py +77 -0
  83. docket_client-0.3.2/docket/models/archive_recurring_acquisition_response.py +83 -0
  84. docket_client-0.3.2/docket/models/archive_scheduled_acquisition_request.py +78 -0
  85. docket_client-0.3.2/docket/models/archive_template_request.py +66 -0
  86. docket_client-0.3.2/docket/models/auth_mode.py +10 -0
  87. docket_client-0.3.2/docket/models/behavior_curriculum_push_settings_request.py +85 -0
  88. docket_client-0.3.2/docket/models/behavior_curriculum_settings.py +55 -0
  89. docket_client-0.3.2/docket/models/behavior_curriculum_settings_preview_response.py +102 -0
  90. docket_client-0.3.2/docket/models/behavior_curriculum_settings_preview_response_promoted.py +50 -0
  91. docket_client-0.3.2/docket/models/behavior_curriculum_settings_version_response.py +160 -0
  92. docket_client-0.3.2/docket/models/behavior_curriculum_settings_version_response_promoted.py +50 -0
  93. docket_client-0.3.2/docket/models/caller_kind.py +11 -0
  94. docket_client-0.3.2/docket/models/create_api_token_request.py +97 -0
  95. docket_client-0.3.2/docket/models/create_application_principal_request.py +65 -0
  96. docket_client-0.3.2/docket/models/create_personal_api_token_request.py +167 -0
  97. docket_client-0.3.2/docket/models/create_recurring_acquisition_request.py +269 -0
  98. docket_client-0.3.2/docket/models/create_role_assignment_request.py +99 -0
  99. docket_client-0.3.2/docket/models/create_scheduled_acquisition_request.py +265 -0
  100. docket_client-0.3.2/docket/models/create_template_request.py +323 -0
  101. docket_client-0.3.2/docket/models/created_api_token_response.py +82 -0
  102. docket_client-0.3.2/docket/models/effective_status.py +15 -0
  103. docket_client-0.3.2/docket/models/fip_color.py +13 -0
  104. docket_client-0.3.2/docket/models/fip_fiber.py +93 -0
  105. docket_client-0.3.2/docket/models/fip_mode.py +9 -0
  106. docket_client-0.3.2/docket/models/fip_push_settings_request.py +82 -0
  107. docket_client-0.3.2/docket/models/fip_settings.py +91 -0
  108. docket_client-0.3.2/docket/models/fip_settings_preview_response.py +97 -0
  109. docket_client-0.3.2/docket/models/fip_settings_preview_response_promoted.py +50 -0
  110. docket_client-0.3.2/docket/models/fip_settings_version_response.py +155 -0
  111. docket_client-0.3.2/docket/models/fip_settings_version_response_promoted.py +50 -0
  112. docket_client-0.3.2/docket/models/future_occurrences.py +9 -0
  113. docket_client-0.3.2/docket/models/get_resolved_settings_response_200.py +65 -0
  114. docket_client-0.3.2/docket/models/held_role_response.py +116 -0
  115. docket_client-0.3.2/docket/models/http_validation_problem_details.py +204 -0
  116. docket_client-0.3.2/docket/models/http_validation_problem_details_errors.py +59 -0
  117. docket_client-0.3.2/docket/models/instrument_response.py +91 -0
  118. docket_client-0.3.2/docket/models/kept_occurrence.py +85 -0
  119. docket_client-0.3.2/docket/models/kept_occurrence_reason.py +10 -0
  120. docket_client-0.3.2/docket/models/list_active_acquisitions_effective_status_item.py +15 -0
  121. docket_client-0.3.2/docket/models/list_scheduled_acquisitions_planning_status.py +11 -0
  122. docket_client-0.3.2/docket/models/me_caller.py +118 -0
  123. docket_client-0.3.2/docket/models/me_permission.py +98 -0
  124. docket_client-0.3.2/docket/models/me_response.py +158 -0
  125. docket_client-0.3.2/docket/models/me_role.py +75 -0
  126. docket_client-0.3.2/docket/models/occurrence_preview.py +121 -0
  127. docket_client-0.3.2/docket/models/occurrence_summary.py +126 -0
  128. docket_client-0.3.2/docket/models/occurrence_warnings.py +91 -0
  129. docket_client-0.3.2/docket/models/permission_action.py +12 -0
  130. docket_client-0.3.2/docket/models/person_assignment.py +74 -0
  131. docket_client-0.3.2/docket/models/person_lookup_response.py +131 -0
  132. docket_client-0.3.2/docket/models/person_response.py +103 -0
  133. docket_client-0.3.2/docket/models/person_role.py +9 -0
  134. docket_client-0.3.2/docket/models/personal_token_limits.py +92 -0
  135. docket_client-0.3.2/docket/models/planning_status.py +11 -0
  136. docket_client-0.3.2/docket/models/principal_kind.py +9 -0
  137. docket_client-0.3.2/docket/models/principal_response.py +216 -0
  138. docket_client-0.3.2/docket/models/problem_details.py +177 -0
  139. docket_client-0.3.2/docket/models/project_response.py +75 -0
  140. docket_client-0.3.2/docket/models/recurrence_exception_kind.py +13 -0
  141. docket_client-0.3.2/docket/models/recurrence_exception_response.py +103 -0
  142. docket_client-0.3.2/docket/models/recurrence_frequency.py +9 -0
  143. docket_client-0.3.2/docket/models/recurrence_preview_response.py +101 -0
  144. docket_client-0.3.2/docket/models/recurrence_rule.py +207 -0
  145. docket_client-0.3.2/docket/models/recurring_acquisition_created_response.py +123 -0
  146. docket_client-0.3.2/docket/models/recurring_acquisition_detail_response.py +105 -0
  147. docket_client-0.3.2/docket/models/recurring_acquisition_response.py +326 -0
  148. docket_client-0.3.2/docket/models/report_run_outcome_request.py +89 -0
  149. docket_client-0.3.2/docket/models/resolution_mode.py +10 -0
  150. docket_client-0.3.2/docket/models/resolved_settings.py +73 -0
  151. docket_client-0.3.2/docket/models/role_assignment_response.py +157 -0
  152. docket_client-0.3.2/docket/models/role_origin.py +10 -0
  153. docket_client-0.3.2/docket/models/role_source_kind.py +10 -0
  154. docket_client-0.3.2/docket/models/run_origin.py +9 -0
  155. docket_client-0.3.2/docket/models/run_outcome.py +9 -0
  156. docket_client-0.3.2/docket/models/run_response.py +181 -0
  157. docket_client-0.3.2/docket/models/run_settings_snapshot.py +72 -0
  158. docket_client-0.3.2/docket/models/run_start_response.py +91 -0
  159. docket_client-0.3.2/docket/models/run_start_response_settings.py +65 -0
  160. docket_client-0.3.2/docket/models/run_status.py +11 -0
  161. docket_client-0.3.2/docket/models/scheduled_acquisition_edit_preview_response.py +150 -0
  162. docket_client-0.3.2/docket/models/scheduled_acquisition_response.py +366 -0
  163. docket_client-0.3.2/docket/models/set_planning_status_request.py +90 -0
  164. docket_client-0.3.2/docket/models/settings_selector.py +138 -0
  165. docket_client-0.3.2/docket/models/skipped_occurrence.py +85 -0
  166. docket_client-0.3.2/docket/models/split_preview_response.py +129 -0
  167. docket_client-0.3.2/docket/models/split_recurring_acquisition_request.py +261 -0
  168. docket_client-0.3.2/docket/models/split_recurring_acquisition_response.py +159 -0
  169. docket_client-0.3.2/docket/models/start_run_request.py +73 -0
  170. docket_client-0.3.2/docket/models/subject_lookup_response.py +149 -0
  171. docket_client-0.3.2/docket/models/subject_response.py +128 -0
  172. docket_client-0.3.2/docket/models/sync_counts.py +80 -0
  173. docket_client-0.3.2/docket/models/sync_response.py +81 -0
  174. docket_client-0.3.2/docket/models/template_response.py +302 -0
  175. docket_client-0.3.2/docket/models/update_scheduled_acquisition_request.py +235 -0
  176. docket_client-0.3.2/docket/models/update_template_request.py +279 -0
  177. docket_client-0.3.2/docket/models/weekday.py +14 -0
  178. docket_client-0.3.2/docket/types.py +54 -0
  179. docket_client-0.3.2/docket_auth/__init__.py +239 -0
  180. docket_client-0.3.2/docket_mcp/__init__.py +6 -0
  181. docket_client-0.3.2/docket_mcp/api.py +207 -0
  182. docket_client-0.3.2/docket_mcp/config.py +57 -0
  183. docket_client-0.3.2/docket_mcp/server.py +78 -0
  184. docket_client-0.3.2/docket_mcp/signin.py +155 -0
  185. docket_client-0.3.2/docket_mcp/slots.py +80 -0
  186. docket_client-0.3.2/docket_mcp/times.py +160 -0
  187. docket_client-0.3.2/docket_mcp/tools.py +1202 -0
  188. docket_client-0.3.2/examples/downstream_usage.py +193 -0
  189. docket_client-0.3.2/pyproject.toml +50 -0
  190. docket_client-0.3.2/tests/docs_fake.py +282 -0
  191. docket_client-0.3.2/tests/fake_docket.py +161 -0
  192. docket_client-0.3.2/tests/test_docket_auth.py +173 -0
  193. docket_client-0.3.2/tests/test_docs_snippets.py +127 -0
  194. docket_client-0.3.2/tests/test_docs_tools.py +89 -0
  195. docket_client-0.3.2/tests/test_mcp_integration.py +173 -0
  196. docket_client-0.3.2/tests/test_mcp_review.py +483 -0
  197. docket_client-0.3.2/tests/test_mcp_stdio.py +113 -0
  198. docket_client-0.3.2/tests/test_mcp_times.py +188 -0
  199. docket_client-0.3.2/tests/test_mcp_tools.py +915 -0
@@ -0,0 +1,26 @@
1
+ __pycache__/
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+
7
+ # pyenv
8
+ .python-version
9
+
10
+ # Environments
11
+ .env
12
+ .venv
13
+
14
+ # mypy
15
+ .mypy_cache/
16
+ .dmypy.json
17
+ dmypy.json
18
+
19
+ # JetBrains
20
+ .idea/
21
+
22
+ /coverage.xml
23
+ /.coverage
24
+
25
+ # ruff cache — generated during openapi-python-client runs
26
+ .ruff_cache/
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.5
2
+ Name: docket-client
3
+ Version: 0.3.2
4
+ Summary: Typed Python client for the docket scheduling API
5
+ Project-URL: Homepage, https://github.com/AllenNeuralDynamics/docket
6
+ Project-URL: Repository, https://github.com/AllenNeuralDynamics/docket
7
+ Project-URL: Bug Tracker, https://github.com/AllenNeuralDynamics/docket/issues
8
+ Project-URL: Changelog, https://github.com/AllenNeuralDynamics/docket/releases
9
+ Author: Allen Institute for Neural Dynamics
10
+ License-Expression: MIT
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: attrs>=22.2.0
22
+ Requires-Dist: httpx<0.29.0,>=0.23.1
23
+ Provides-Extra: auth
24
+ Requires-Dist: msal<2,>=1.31; extra == 'auth'
25
+ Provides-Extra: mcp
26
+ Requires-Dist: mcp<2,>=1.30; extra == 'mcp'
27
+ Requires-Dist: msal<2,>=1.31; extra == 'mcp'
28
+ Requires-Dist: tzdata>=2024.1; (sys_platform == 'win32') and extra == 'mcp'
29
+ Requires-Dist: tzlocal<6,>=5; extra == 'mcp'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # docket
33
+
34
+ A typed Python client for the **docket** scheduling API.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install docket-client
40
+ # the unreleased version, from the repository:
41
+ pip install "docket-client @ git+https://github.com/AllenNeuralDynamics/docket#subdirectory=client"
42
+ # or, from the repo root during development:
43
+ pip install ./client
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```python
49
+ from docket import Client
50
+
51
+ client = Client(base_url="http://localhost:8001")
52
+ ```
53
+
54
+ For authenticated endpoints use `AuthenticatedClient`:
55
+
56
+ ```python
57
+ from docket import AuthenticatedClient
58
+
59
+ client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
60
+ ```
61
+
62
+ Call any endpoint:
63
+
64
+ ```python
65
+ from docket.api.scheduled_acquisitions import list_active_acquisitions
66
+
67
+ sessions = list_active_acquisitions.sync(
68
+ client=client,
69
+ instrument_id="BEH.B-Box1",
70
+ before_minutes=15,
71
+ effective_status=["scheduled", "ongoing"],
72
+ )
73
+ ```
74
+
75
+ Every endpoint module exposes four variants:
76
+
77
+ | Function | Behaviour |
78
+ | --- | --- |
79
+ | `sync` | Blocking — returns the parsed response body or `None` |
80
+ | `sync_detailed` | Blocking — always returns a `Response` object with status code, headers, and `parsed` |
81
+ | `asyncio` | Async equivalent of `sync` |
82
+ | `asyncio_detailed` | Async equivalent of `sync_detailed` |
83
+
84
+ See [examples/downstream_usage.py](examples/downstream_usage.py) for a
85
+ full end-to-end walkthrough.
86
+
87
+ ## MCP server (`docket-mcp`)
88
+
89
+ `docket-mcp` lets an AI assistant (Claude Code, Claude Desktop, any MCP client) look at the
90
+ schedule and change it, through the same API as everyone else. It runs **locally over stdio**:
91
+ the client starts it on your machine, so it reaches Docket over your own network or VPN and
92
+ nothing new is hosted. It is the `docket_mcp` package here, next to `docket_auth`.
93
+
94
+ ### Install
95
+
96
+ ```bash
97
+ pip install "docket-client[mcp]"
98
+ # or as an isolated tool (puts `docket-mcp` on your PATH):
99
+ uv tool install "docket-client[mcp]"
100
+ # from a checkout:
101
+ uv tool install "./client[mcp]"
102
+ ```
103
+
104
+ ### Add it to your assistant
105
+
106
+ Claude Code:
107
+
108
+ ```bash
109
+ claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -e DOCKET_TIME_ZONE=America/Los_Angeles -- docket-mcp
110
+ # with an API token (see "Sign-in"):
111
+ claude mcp add docket -e DOCKET_API_URL=https://docket.example.org -e DOCKET_API_TOKEN=dkt_... -- docket-mcp
112
+ # straight from a checkout, without installing:
113
+ claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -- uv run --project ./client --extra mcp docket-mcp
114
+ ```
115
+
116
+ Claude Desktop (`claude_desktop_config.json`):
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "docket": {
122
+ "command": "docket-mcp",
123
+ "env": {
124
+ "DOCKET_API_URL": "http://localhost:8001",
125
+ "DOCKET_TIME_ZONE": "America/Los_Angeles",
126
+ "DOCKET_API_TOKEN": "dkt_..."
127
+ }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ If `docket-mcp` is not on the app's `PATH`, give its full path as `command`.
134
+
135
+ ### Configuration
136
+
137
+ All through environment variables:
138
+
139
+ | Variable | Meaning |
140
+ | --- | --- |
141
+ | `DOCKET_API_URL` | The API's address. Default `http://localhost:8001`. A frontend origin works too (`https://docket.example.org` or `…/api`), with its URL prefix if it has one (`https://host/docket`). |
142
+ | `DOCKET_TIME_ZONE` | IANA zone for the local times you give and get back. Default: this machine's zone. A tool's `time_zone` argument overrides it. |
143
+ | `DOCKET_API_TOKEN` | A Docket API token (`dkt_…`). Checked **first**; works in Tokens and Entra mode. |
144
+ | `DOCKET_AUTH_CLIENT_ID`, `DOCKET_AUTH_SCOPE`, `DOCKET_AUTH_TENANT_ID` (or `DOCKET_AUTH_AUTHORITY`) | Entra sign-in when there is no API token: the SPA's public client id, `api://…/access_as_user`, and the tenant (or another OIDC authority, such as the mock provider). |
145
+ | `DOCKET_AUTH_CLIENT_SECRET` | With the three above: sign in as an application (client credentials) instead of as a person. |
146
+ | `DOCKET_AUTH_CACHE_PATH` | Where the person's token cache lives. Default `~/.docket/mcp-token-cache.json` (holds refresh tokens: protect it). |
147
+
148
+ ### Sign-in
149
+
150
+ Reads are anonymous. The server asks `/api/v1/me` once for `auth_mode`, and only **writes** carry a token.
151
+
152
+ - `auth_mode: disabled` (the default Docket): nothing to set up and you are never asked to sign in.
153
+ - `auth_mode: tokens`: set `DOCKET_API_TOKEN`. Ask an admin for a token from **Settings > Access**. Without one, write tools say so.
154
+ - `auth_mode: entra`: set `DOCKET_API_TOKEN` too (simplest), or sign in as yourself. The first write prints a URL and a code to stderr **and** returns them as the tool's error text (some clients hide stderr); open the URL, enter the code, then run the tool again. The token is cached afterwards. Set `DOCKET_AUTH_CLIENT_SECRET` for an unattended application.
155
+
156
+ Tokens are never logged or returned by a tool. `whoami` shows your auth mode, caller, roles (and where each came from) and grants: as yourself when a credential is available without interaction (an API token, client credentials or a cached sign-in), otherwise as anonymous (`signed_in: false`). It never starts a sign-in.
157
+
158
+ The device-code token cache holds refresh tokens. It is created owner-only (`chmod 600`) on Linux and macOS; on Windows `chmod` is a no-op and the file relies on your user profile's ACL, so keep it there (the default is `~/.docket/`).
159
+
160
+ ### Tools
161
+
162
+ | Read | |
163
+ | --- | --- |
164
+ | `list_rigs`, `list_projects` | what you can book, with optional `search` |
165
+ | `lookup_subject`, `lookup_person` | check an id or username exists |
166
+ | `get_schedule` | sessions between two dates (rig, subject, person, archived filters) |
167
+ | `get_acquisition` | one session in full: people, notes, resolved settings, runs |
168
+ | `find_free_slots` | gaps on a rig long enough for a session, within daily bounds |
169
+ | `list_series`, `get_series` | recurring series and their rules |
170
+ | `list_templates`, `list_settings`, `list_runs`, `whoami` | |
171
+
172
+ | Write (all take `dry_run`, default **true**) | |
173
+ | --- | --- |
174
+ | `schedule_acquisition` | book one session (optionally from a template) |
175
+ | `move_acquisition` | change start, length or rig |
176
+ | `archive_acquisition` | archive a session (nothing is ever hard-deleted) |
177
+ | `create_series` | a finite daily/weekly series |
178
+ | `edit_series_from` | change a series from a date on (the old one ends, a new one starts) |
179
+ | `archive_series` | archive a series, keeping or archiving its upcoming sessions |
180
+ | `mark_outcome` | record succeeded / failed / cancelled by hand |
181
+
182
+ Not in v1: pushing settings, syncing vocabulary, managing templates and roles.
183
+
184
+ ### Dry runs
185
+
186
+ Every write tool previews by default and changes nothing: it returns what would happen,
187
+ including overlap warnings, skipped (daylight-saving) dates and which sessions a series edit
188
+ would replace or keep. The tool descriptions tell the assistant to show you the preview and
189
+ repeat the call with `dry_run=false` only once you agree. Series and moves use the server's own
190
+ preview endpoints; for a single session the API has none, so the preview checks the schedule
191
+ itself with the server's overlap rule (same rig only; the same subject on another rig is listed
192
+ as `advisory`) and the server checks again when it creates it.
193
+
194
+ Each preview returns what the confirmation needs, under `confirm_with`: the `expected_version`
195
+ of what it looked at (moves, archives, outcomes, series edits and archives) and an
196
+ `idempotency_key` (new sessions, series and series edits). Repeat the call with
197
+ `dry_run=false` and those values. If the session or series changed in between, the call is
198
+ refused with "changed since your preview; preview again" and nothing is written. A retry
199
+ after a timeout with the same key cannot book or create twice.
200
+
201
+ ### Times
202
+
203
+ Give local wall-clock times such as `2026-03-08T09:30`, never offsets. Results show the local
204
+ time (with its offset) and UTC. A time that does not exist (a spring-forward gap) is refused;
205
+ a repeated one (fall back) takes the first occurrence and says so.
206
+
207
+ ### Example prompts
208
+
209
+ - "What is on rig `BEH.A` next week, and which afternoons are free for a 90-minute session?"
210
+ - "Book subject 812345 on `BEH.A` under project `Vision` tomorrow at 10:00 for an hour, with me as experimenter." (it previews first)
211
+ - "Move session 412 to Thursday at 14:00."
212
+ - "Set up a weekly series, Mondays and Thursdays at 09:00 for eight sessions, for subject 812345."
213
+ - "From 2026-11-02, shift that series to 10:30." / "Cancel the rest of series 17 but keep what already ran."
214
+ - "Did anything fail on the FIP rigs this week?"
215
+
216
+ ### Developing
217
+
218
+ ```bash
219
+ uv sync --group dev
220
+ uv run pytest client/tests # unit tests + the stdio smoke test
221
+ uv run pytest client/tests -m integration # schedule -> move -> archive on a real server
222
+ ```
223
+
224
+ The integration test builds the server image and starts its own Compose project
225
+ (`docket-mcp-it`, API on port 8020, static vocabulary, nothing from the institute) and removes
226
+ it afterwards; set `DOCKET_IT_URL` to use a server that is already running instead.
227
+
228
+ ## Building / publishing
229
+
230
+ ```bash
231
+ # from the repo root
232
+ uv build --package docket-client # → dist/docket_client-*.whl
233
+ ```
@@ -0,0 +1,202 @@
1
+ # docket
2
+
3
+ A typed Python client for the **docket** scheduling API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install docket-client
9
+ # the unreleased version, from the repository:
10
+ pip install "docket-client @ git+https://github.com/AllenNeuralDynamics/docket#subdirectory=client"
11
+ # or, from the repo root during development:
12
+ pip install ./client
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ from docket import Client
19
+
20
+ client = Client(base_url="http://localhost:8001")
21
+ ```
22
+
23
+ For authenticated endpoints use `AuthenticatedClient`:
24
+
25
+ ```python
26
+ from docket import AuthenticatedClient
27
+
28
+ client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
29
+ ```
30
+
31
+ Call any endpoint:
32
+
33
+ ```python
34
+ from docket.api.scheduled_acquisitions import list_active_acquisitions
35
+
36
+ sessions = list_active_acquisitions.sync(
37
+ client=client,
38
+ instrument_id="BEH.B-Box1",
39
+ before_minutes=15,
40
+ effective_status=["scheduled", "ongoing"],
41
+ )
42
+ ```
43
+
44
+ Every endpoint module exposes four variants:
45
+
46
+ | Function | Behaviour |
47
+ | --- | --- |
48
+ | `sync` | Blocking — returns the parsed response body or `None` |
49
+ | `sync_detailed` | Blocking — always returns a `Response` object with status code, headers, and `parsed` |
50
+ | `asyncio` | Async equivalent of `sync` |
51
+ | `asyncio_detailed` | Async equivalent of `sync_detailed` |
52
+
53
+ See [examples/downstream_usage.py](examples/downstream_usage.py) for a
54
+ full end-to-end walkthrough.
55
+
56
+ ## MCP server (`docket-mcp`)
57
+
58
+ `docket-mcp` lets an AI assistant (Claude Code, Claude Desktop, any MCP client) look at the
59
+ schedule and change it, through the same API as everyone else. It runs **locally over stdio**:
60
+ the client starts it on your machine, so it reaches Docket over your own network or VPN and
61
+ nothing new is hosted. It is the `docket_mcp` package here, next to `docket_auth`.
62
+
63
+ ### Install
64
+
65
+ ```bash
66
+ pip install "docket-client[mcp]"
67
+ # or as an isolated tool (puts `docket-mcp` on your PATH):
68
+ uv tool install "docket-client[mcp]"
69
+ # from a checkout:
70
+ uv tool install "./client[mcp]"
71
+ ```
72
+
73
+ ### Add it to your assistant
74
+
75
+ Claude Code:
76
+
77
+ ```bash
78
+ claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -e DOCKET_TIME_ZONE=America/Los_Angeles -- docket-mcp
79
+ # with an API token (see "Sign-in"):
80
+ claude mcp add docket -e DOCKET_API_URL=https://docket.example.org -e DOCKET_API_TOKEN=dkt_... -- docket-mcp
81
+ # straight from a checkout, without installing:
82
+ claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -- uv run --project ./client --extra mcp docket-mcp
83
+ ```
84
+
85
+ Claude Desktop (`claude_desktop_config.json`):
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "docket": {
91
+ "command": "docket-mcp",
92
+ "env": {
93
+ "DOCKET_API_URL": "http://localhost:8001",
94
+ "DOCKET_TIME_ZONE": "America/Los_Angeles",
95
+ "DOCKET_API_TOKEN": "dkt_..."
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ If `docket-mcp` is not on the app's `PATH`, give its full path as `command`.
103
+
104
+ ### Configuration
105
+
106
+ All through environment variables:
107
+
108
+ | Variable | Meaning |
109
+ | --- | --- |
110
+ | `DOCKET_API_URL` | The API's address. Default `http://localhost:8001`. A frontend origin works too (`https://docket.example.org` or `…/api`), with its URL prefix if it has one (`https://host/docket`). |
111
+ | `DOCKET_TIME_ZONE` | IANA zone for the local times you give and get back. Default: this machine's zone. A tool's `time_zone` argument overrides it. |
112
+ | `DOCKET_API_TOKEN` | A Docket API token (`dkt_…`). Checked **first**; works in Tokens and Entra mode. |
113
+ | `DOCKET_AUTH_CLIENT_ID`, `DOCKET_AUTH_SCOPE`, `DOCKET_AUTH_TENANT_ID` (or `DOCKET_AUTH_AUTHORITY`) | Entra sign-in when there is no API token: the SPA's public client id, `api://…/access_as_user`, and the tenant (or another OIDC authority, such as the mock provider). |
114
+ | `DOCKET_AUTH_CLIENT_SECRET` | With the three above: sign in as an application (client credentials) instead of as a person. |
115
+ | `DOCKET_AUTH_CACHE_PATH` | Where the person's token cache lives. Default `~/.docket/mcp-token-cache.json` (holds refresh tokens: protect it). |
116
+
117
+ ### Sign-in
118
+
119
+ Reads are anonymous. The server asks `/api/v1/me` once for `auth_mode`, and only **writes** carry a token.
120
+
121
+ - `auth_mode: disabled` (the default Docket): nothing to set up and you are never asked to sign in.
122
+ - `auth_mode: tokens`: set `DOCKET_API_TOKEN`. Ask an admin for a token from **Settings > Access**. Without one, write tools say so.
123
+ - `auth_mode: entra`: set `DOCKET_API_TOKEN` too (simplest), or sign in as yourself. The first write prints a URL and a code to stderr **and** returns them as the tool's error text (some clients hide stderr); open the URL, enter the code, then run the tool again. The token is cached afterwards. Set `DOCKET_AUTH_CLIENT_SECRET` for an unattended application.
124
+
125
+ Tokens are never logged or returned by a tool. `whoami` shows your auth mode, caller, roles (and where each came from) and grants: as yourself when a credential is available without interaction (an API token, client credentials or a cached sign-in), otherwise as anonymous (`signed_in: false`). It never starts a sign-in.
126
+
127
+ The device-code token cache holds refresh tokens. It is created owner-only (`chmod 600`) on Linux and macOS; on Windows `chmod` is a no-op and the file relies on your user profile's ACL, so keep it there (the default is `~/.docket/`).
128
+
129
+ ### Tools
130
+
131
+ | Read | |
132
+ | --- | --- |
133
+ | `list_rigs`, `list_projects` | what you can book, with optional `search` |
134
+ | `lookup_subject`, `lookup_person` | check an id or username exists |
135
+ | `get_schedule` | sessions between two dates (rig, subject, person, archived filters) |
136
+ | `get_acquisition` | one session in full: people, notes, resolved settings, runs |
137
+ | `find_free_slots` | gaps on a rig long enough for a session, within daily bounds |
138
+ | `list_series`, `get_series` | recurring series and their rules |
139
+ | `list_templates`, `list_settings`, `list_runs`, `whoami` | |
140
+
141
+ | Write (all take `dry_run`, default **true**) | |
142
+ | --- | --- |
143
+ | `schedule_acquisition` | book one session (optionally from a template) |
144
+ | `move_acquisition` | change start, length or rig |
145
+ | `archive_acquisition` | archive a session (nothing is ever hard-deleted) |
146
+ | `create_series` | a finite daily/weekly series |
147
+ | `edit_series_from` | change a series from a date on (the old one ends, a new one starts) |
148
+ | `archive_series` | archive a series, keeping or archiving its upcoming sessions |
149
+ | `mark_outcome` | record succeeded / failed / cancelled by hand |
150
+
151
+ Not in v1: pushing settings, syncing vocabulary, managing templates and roles.
152
+
153
+ ### Dry runs
154
+
155
+ Every write tool previews by default and changes nothing: it returns what would happen,
156
+ including overlap warnings, skipped (daylight-saving) dates and which sessions a series edit
157
+ would replace or keep. The tool descriptions tell the assistant to show you the preview and
158
+ repeat the call with `dry_run=false` only once you agree. Series and moves use the server's own
159
+ preview endpoints; for a single session the API has none, so the preview checks the schedule
160
+ itself with the server's overlap rule (same rig only; the same subject on another rig is listed
161
+ as `advisory`) and the server checks again when it creates it.
162
+
163
+ Each preview returns what the confirmation needs, under `confirm_with`: the `expected_version`
164
+ of what it looked at (moves, archives, outcomes, series edits and archives) and an
165
+ `idempotency_key` (new sessions, series and series edits). Repeat the call with
166
+ `dry_run=false` and those values. If the session or series changed in between, the call is
167
+ refused with "changed since your preview; preview again" and nothing is written. A retry
168
+ after a timeout with the same key cannot book or create twice.
169
+
170
+ ### Times
171
+
172
+ Give local wall-clock times such as `2026-03-08T09:30`, never offsets. Results show the local
173
+ time (with its offset) and UTC. A time that does not exist (a spring-forward gap) is refused;
174
+ a repeated one (fall back) takes the first occurrence and says so.
175
+
176
+ ### Example prompts
177
+
178
+ - "What is on rig `BEH.A` next week, and which afternoons are free for a 90-minute session?"
179
+ - "Book subject 812345 on `BEH.A` under project `Vision` tomorrow at 10:00 for an hour, with me as experimenter." (it previews first)
180
+ - "Move session 412 to Thursday at 14:00."
181
+ - "Set up a weekly series, Mondays and Thursdays at 09:00 for eight sessions, for subject 812345."
182
+ - "From 2026-11-02, shift that series to 10:30." / "Cancel the rest of series 17 but keep what already ran."
183
+ - "Did anything fail on the FIP rigs this week?"
184
+
185
+ ### Developing
186
+
187
+ ```bash
188
+ uv sync --group dev
189
+ uv run pytest client/tests # unit tests + the stdio smoke test
190
+ uv run pytest client/tests -m integration # schedule -> move -> archive on a real server
191
+ ```
192
+
193
+ The integration test builds the server image and starts its own Compose project
194
+ (`docket-mcp-it`, API on port 8020, static vocabulary, nothing from the institute) and removes
195
+ it afterwards; set `DOCKET_IT_URL` to use a server that is already running instead.
196
+
197
+ ## Building / publishing
198
+
199
+ ```bash
200
+ # from the repo root
201
+ uv build --package docket-client # → dist/docket_client-*.whl
202
+ ```
@@ -0,0 +1,8 @@
1
+ """A client library for accessing Docket API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,193 @@
1
+ from http import HTTPStatus
2
+ from typing import Any, cast
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...types import Response, UNSET
9
+ from ... import errors
10
+
11
+ from ...models.add_principal_request import AddPrincipalRequest
12
+ from ...models.http_validation_problem_details import HttpValidationProblemDetails
13
+ from ...models.principal_response import PrincipalResponse
14
+ from ...models.problem_details import ProblemDetails
15
+ from typing import cast
16
+
17
+
18
+ def _get_kwargs(
19
+ *,
20
+ body: AddPrincipalRequest,
21
+ ) -> dict[str, Any]:
22
+ headers: dict[str, Any] = {}
23
+
24
+ _kwargs: dict[str, Any] = {
25
+ "method": "post",
26
+ "url": "/api/v1/access/principals",
27
+ }
28
+
29
+ _kwargs["json"] = body.to_dict()
30
+
31
+ headers["Content-Type"] = "application/json"
32
+
33
+ _kwargs["headers"] = headers
34
+ return _kwargs
35
+
36
+
37
+ def _parse_response(
38
+ *, client: AuthenticatedClient | Client, response: httpx.Response
39
+ ) -> HttpValidationProblemDetails | PrincipalResponse | ProblemDetails | None:
40
+ if response.status_code == 201:
41
+ response_201 = PrincipalResponse.from_dict(response.json())
42
+
43
+ return response_201
44
+
45
+ if response.status_code == 401:
46
+ response_401 = ProblemDetails.from_dict(response.json())
47
+
48
+ return response_401
49
+
50
+ if response.status_code == 403:
51
+ response_403 = ProblemDetails.from_dict(response.json())
52
+
53
+ return response_403
54
+
55
+ if response.status_code == 409:
56
+ response_409 = ProblemDetails.from_dict(response.json())
57
+
58
+ return response_409
59
+
60
+ if response.status_code == 422:
61
+ response_422 = HttpValidationProblemDetails.from_dict(response.json())
62
+
63
+ return response_422
64
+
65
+ if client.raise_on_unexpected_status:
66
+ raise errors.UnexpectedStatus(response.status_code, response.content)
67
+ else:
68
+ return None
69
+
70
+
71
+ def _build_response(
72
+ *, client: AuthenticatedClient | Client, response: httpx.Response
73
+ ) -> Response[HttpValidationProblemDetails | PrincipalResponse | ProblemDetails]:
74
+ return Response(
75
+ status_code=HTTPStatus(response.status_code),
76
+ content=response.content,
77
+ headers=response.headers,
78
+ parsed=_parse_response(client=client, response=response),
79
+ )
80
+
81
+
82
+ def sync_detailed(
83
+ *,
84
+ client: AuthenticatedClient,
85
+ body: AddPrincipalRequest,
86
+ ) -> Response[HttpValidationProblemDetails | PrincipalResponse | ProblemDetails]:
87
+ """Register a principal by Entra object id before it has signed in (how rig applications with an Entra
88
+ registration are added). Admin only.
89
+
90
+ Args:
91
+ body (AddPrincipalRequest): Registers a principal before it has signed in (how rig
92
+ applications are added).
93
+
94
+ Raises:
95
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
96
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
97
+
98
+ Returns:
99
+ Response[HttpValidationProblemDetails | PrincipalResponse | ProblemDetails]
100
+ """
101
+
102
+ kwargs = _get_kwargs(
103
+ body=body,
104
+ )
105
+
106
+ response = client.get_httpx_client().request(
107
+ **kwargs,
108
+ )
109
+
110
+ return _build_response(client=client, response=response)
111
+
112
+
113
+ def sync(
114
+ *,
115
+ client: AuthenticatedClient,
116
+ body: AddPrincipalRequest,
117
+ ) -> HttpValidationProblemDetails | PrincipalResponse | ProblemDetails | None:
118
+ """Register a principal by Entra object id before it has signed in (how rig applications with an Entra
119
+ registration are added). Admin only.
120
+
121
+ Args:
122
+ body (AddPrincipalRequest): Registers a principal before it has signed in (how rig
123
+ applications are added).
124
+
125
+ Raises:
126
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
127
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
128
+
129
+ Returns:
130
+ HttpValidationProblemDetails | PrincipalResponse | ProblemDetails
131
+ """
132
+
133
+ return sync_detailed(
134
+ client=client,
135
+ body=body,
136
+ ).parsed
137
+
138
+
139
+ async def asyncio_detailed(
140
+ *,
141
+ client: AuthenticatedClient,
142
+ body: AddPrincipalRequest,
143
+ ) -> Response[HttpValidationProblemDetails | PrincipalResponse | ProblemDetails]:
144
+ """Register a principal by Entra object id before it has signed in (how rig applications with an Entra
145
+ registration are added). Admin only.
146
+
147
+ Args:
148
+ body (AddPrincipalRequest): Registers a principal before it has signed in (how rig
149
+ applications are added).
150
+
151
+ Raises:
152
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
153
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
154
+
155
+ Returns:
156
+ Response[HttpValidationProblemDetails | PrincipalResponse | ProblemDetails]
157
+ """
158
+
159
+ kwargs = _get_kwargs(
160
+ body=body,
161
+ )
162
+
163
+ response = await client.get_async_httpx_client().request(**kwargs)
164
+
165
+ return _build_response(client=client, response=response)
166
+
167
+
168
+ async def asyncio(
169
+ *,
170
+ client: AuthenticatedClient,
171
+ body: AddPrincipalRequest,
172
+ ) -> HttpValidationProblemDetails | PrincipalResponse | ProblemDetails | None:
173
+ """Register a principal by Entra object id before it has signed in (how rig applications with an Entra
174
+ registration are added). Admin only.
175
+
176
+ Args:
177
+ body (AddPrincipalRequest): Registers a principal before it has signed in (how rig
178
+ applications are added).
179
+
180
+ Raises:
181
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
182
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
183
+
184
+ Returns:
185
+ HttpValidationProblemDetails | PrincipalResponse | ProblemDetails
186
+ """
187
+
188
+ return (
189
+ await asyncio_detailed(
190
+ client=client,
191
+ body=body,
192
+ )
193
+ ).parsed