spatialflow 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. spatialflow/__init__.py +91 -0
  2. spatialflow/_generated/.github/workflows/python.yml +31 -0
  3. spatialflow/_generated/.gitignore +66 -0
  4. spatialflow/_generated/.gitlab-ci.yml +31 -0
  5. spatialflow/_generated/.openapi-generator/FILES +390 -0
  6. spatialflow/_generated/.openapi-generator/VERSION +1 -0
  7. spatialflow/_generated/.openapi-generator-ignore +23 -0
  8. spatialflow/_generated/.spec-hash +1 -0
  9. spatialflow/_generated/.travis.yml +17 -0
  10. spatialflow/_generated/README.md +537 -0
  11. spatialflow/_generated/__init__.py +1 -0
  12. spatialflow/_generated/docs/APIUsageStats.md +32 -0
  13. spatialflow/_generated/docs/AccountApi.md +1751 -0
  14. spatialflow/_generated/docs/ActionDeliverySuccessMetrics.md +32 -0
  15. spatialflow/_generated/docs/ActionResponse.md +32 -0
  16. spatialflow/_generated/docs/ActionRetryConfigSchema.md +32 -0
  17. spatialflow/_generated/docs/ActivitySummary.md +30 -0
  18. spatialflow/_generated/docs/AdminApi.md +1787 -0
  19. spatialflow/_generated/docs/ApiKeyCreateRequest.md +31 -0
  20. spatialflow/_generated/docs/ApiKeyCreateResponse.md +31 -0
  21. spatialflow/_generated/docs/ApiKeyResponse.md +38 -0
  22. spatialflow/_generated/docs/ApiKeyUpdateRequest.md +33 -0
  23. spatialflow/_generated/docs/AsyncUploadGeofencesResponse.md +33 -0
  24. spatialflow/_generated/docs/AuthTypeEnum.md +17 -0
  25. spatialflow/_generated/docs/AuthenticationApi.md +1289 -0
  26. spatialflow/_generated/docs/BatchLocationUpdateIn.md +30 -0
  27. spatialflow/_generated/docs/BillingApi.md +925 -0
  28. spatialflow/_generated/docs/BulkGeofenceRequest.md +29 -0
  29. spatialflow/_generated/docs/ChangePasswordSchema.md +30 -0
  30. spatialflow/_generated/docs/CheckoutSessionRequest.md +32 -0
  31. spatialflow/_generated/docs/CheckoutSessionResponse.md +31 -0
  32. spatialflow/_generated/docs/CircuitBreakerSchema.md +33 -0
  33. spatialflow/_generated/docs/ConfigFieldDefinitionRequest.md +41 -0
  34. spatialflow/_generated/docs/ConfigFieldDefinitionResponse.md +43 -0
  35. spatialflow/_generated/docs/ConfirmPasswordResetSchema.md +31 -0
  36. spatialflow/_generated/docs/CreateFromTemplateIn.md +30 -0
  37. spatialflow/_generated/docs/CreateGeofenceRequest.md +36 -0
  38. spatialflow/_generated/docs/CreateIntegrationSchema.md +33 -0
  39. spatialflow/_generated/docs/CreateUserSchema.md +35 -0
  40. spatialflow/_generated/docs/CreateWebhookRequest.md +43 -0
  41. spatialflow/_generated/docs/DashboardComparisonMetrics.md +32 -0
  42. spatialflow/_generated/docs/DashboardMetricsResponse.md +36 -0
  43. spatialflow/_generated/docs/DefaultApi.md +585 -0
  44. spatialflow/_generated/docs/DeleteFileResponse.md +30 -0
  45. spatialflow/_generated/docs/DeliveryStatusEnum.md +15 -0
  46. spatialflow/_generated/docs/DeviceIn.md +32 -0
  47. spatialflow/_generated/docs/DeviceOut.md +37 -0
  48. spatialflow/_generated/docs/DevicesApi.md +1213 -0
  49. spatialflow/_generated/docs/E2ETestApi.md +271 -0
  50. spatialflow/_generated/docs/EmailApi.md +541 -0
  51. spatialflow/_generated/docs/EmailHealthResponse.md +31 -0
  52. spatialflow/_generated/docs/EmailQueueStats.md +34 -0
  53. spatialflow/_generated/docs/EmailStats.md +31 -0
  54. spatialflow/_generated/docs/EmailStatusResponse.md +36 -0
  55. spatialflow/_generated/docs/ErrorResponse.md +32 -0
  56. spatialflow/_generated/docs/ExecutionOut.md +38 -0
  57. spatialflow/_generated/docs/ExportIntegrationSchema.md +35 -0
  58. spatialflow/_generated/docs/FileListResponse.md +31 -0
  59. spatialflow/_generated/docs/ForgotPasswordSchema.md +29 -0
  60. spatialflow/_generated/docs/GPXPlaybackOut.md +42 -0
  61. spatialflow/_generated/docs/GPXRouteOut.md +40 -0
  62. spatialflow/_generated/docs/GPXSimulatorApi.md +883 -0
  63. spatialflow/_generated/docs/GeoJSONPoint.md +31 -0
  64. spatialflow/_generated/docs/GeofenceListResponse.md +31 -0
  65. spatialflow/_generated/docs/GeofenceResponse.md +43 -0
  66. spatialflow/_generated/docs/GeofenceStats.md +30 -0
  67. spatialflow/_generated/docs/GeofenceTestResult.md +33 -0
  68. spatialflow/_generated/docs/GeofencesApi.md +1524 -0
  69. spatialflow/_generated/docs/HealthCheckResponse.md +31 -0
  70. spatialflow/_generated/docs/HealthResponse.md +32 -0
  71. spatialflow/_generated/docs/ImportIntegrationSchema.md +32 -0
  72. spatialflow/_generated/docs/ImportResultSchema.md +33 -0
  73. spatialflow/_generated/docs/IntegrationDetailSchema.md +44 -0
  74. spatialflow/_generated/docs/IntegrationResponseSchema.md +42 -0
  75. spatialflow/_generated/docs/IntegrationStatsSchema.md +36 -0
  76. spatialflow/_generated/docs/IntegrationTypeListResponse.md +34 -0
  77. spatialflow/_generated/docs/IntegrationTypeRequest.md +40 -0
  78. spatialflow/_generated/docs/IntegrationTypeResponse.md +45 -0
  79. spatialflow/_generated/docs/IntegrationsApi.md +2008 -0
  80. spatialflow/_generated/docs/InvoiceLineItem.md +33 -0
  81. spatialflow/_generated/docs/InvoiceListResponse.md +31 -0
  82. spatialflow/_generated/docs/InvoiceResponse.md +40 -0
  83. spatialflow/_generated/docs/LocationBatchIn.md +31 -0
  84. spatialflow/_generated/docs/LocationImportResponse.md +41 -0
  85. spatialflow/_generated/docs/LocationIngestResponse.md +35 -0
  86. spatialflow/_generated/docs/LocationPointIn.md +38 -0
  87. spatialflow/_generated/docs/LocationUpdateIn.md +36 -0
  88. spatialflow/_generated/docs/LocationUpdateOut.md +32 -0
  89. spatialflow/_generated/docs/LoginResponse.md +33 -0
  90. spatialflow/_generated/docs/LoginSchema.md +30 -0
  91. spatialflow/_generated/docs/MemberActionResponse.md +33 -0
  92. spatialflow/_generated/docs/MemberSummary.md +36 -0
  93. spatialflow/_generated/docs/MethodEnum.md +17 -0
  94. spatialflow/_generated/docs/OAuthAuthorizeResponse.md +29 -0
  95. spatialflow/_generated/docs/OAuthCallbackQuery.md +32 -0
  96. spatialflow/_generated/docs/OAuthLinkResponse.md +31 -0
  97. spatialflow/_generated/docs/OAuthProvidersResponse.md +29 -0
  98. spatialflow/_generated/docs/OnboardingProgressResponse.md +37 -0
  99. spatialflow/_generated/docs/PaymentMethodResponse.md +37 -0
  100. spatialflow/_generated/docs/PingResponse.md +31 -0
  101. spatialflow/_generated/docs/PlanChangePreviewResponse.md +35 -0
  102. spatialflow/_generated/docs/PlanFeatures.md +36 -0
  103. spatialflow/_generated/docs/PlanLimits.md +34 -0
  104. spatialflow/_generated/docs/PlanResponse.md +37 -0
  105. spatialflow/_generated/docs/PortalSessionRequest.md +30 -0
  106. spatialflow/_generated/docs/PortalSessionResponse.md +30 -0
  107. spatialflow/_generated/docs/PresignedUrlRequest.md +31 -0
  108. spatialflow/_generated/docs/PresignedUrlResponse.md +35 -0
  109. spatialflow/_generated/docs/PrivacyErasureRequest.md +35 -0
  110. spatialflow/_generated/docs/PrivacyErasureResponse.md +36 -0
  111. spatialflow/_generated/docs/PublicApi.md +389 -0
  112. spatialflow/_generated/docs/PublicLocationIngestApi.md +249 -0
  113. spatialflow/_generated/docs/RateLimitResponse.md +32 -0
  114. spatialflow/_generated/docs/RecentActivity.md +31 -0
  115. spatialflow/_generated/docs/RefreshTokenSchema.md +29 -0
  116. spatialflow/_generated/docs/RegisterSchema.md +37 -0
  117. spatialflow/_generated/docs/ResendVerificationSchema.md +29 -0
  118. spatialflow/_generated/docs/ResetPasswordSchema.md +31 -0
  119. spatialflow/_generated/docs/RetryPolicyResponseSchema.md +32 -0
  120. spatialflow/_generated/docs/RetryPolicySchema.md +35 -0
  121. spatialflow/_generated/docs/RetryStrategyEnum.md +14 -0
  122. spatialflow/_generated/docs/SeedDataResponseSchema.md +31 -0
  123. spatialflow/_generated/docs/SendEmailRequest.md +33 -0
  124. spatialflow/_generated/docs/SetupIntentResponse.md +30 -0
  125. spatialflow/_generated/docs/SignupRequest.md +40 -0
  126. spatialflow/_generated/docs/StartPlaybackRequest.md +30 -0
  127. spatialflow/_generated/docs/StorageApi.md +494 -0
  128. spatialflow/_generated/docs/SubscriptionActionResponse.md +32 -0
  129. spatialflow/_generated/docs/SubscriptionResponse.md +36 -0
  130. spatialflow/_generated/docs/SubscriptionsApi.md +677 -0
  131. spatialflow/_generated/docs/SuccessResponse.md +31 -0
  132. spatialflow/_generated/docs/SystemApi.md +137 -0
  133. spatialflow/_generated/docs/TemplateOut.md +35 -0
  134. spatialflow/_generated/docs/TestEventRequest.md +29 -0
  135. spatialflow/_generated/docs/TestIntegrationResponseSchema.md +31 -0
  136. spatialflow/_generated/docs/TestPointRequest.md +34 -0
  137. spatialflow/_generated/docs/TestPointResponse.md +33 -0
  138. spatialflow/_generated/docs/TestWebhookRequest.md +32 -0
  139. spatialflow/_generated/docs/TestWorkflowIn.md +29 -0
  140. spatialflow/_generated/docs/TileMetadata.md +36 -0
  141. spatialflow/_generated/docs/TilesApi.md +462 -0
  142. spatialflow/_generated/docs/UnsubscribeRequest.md +29 -0
  143. spatialflow/_generated/docs/UnsubscribeResponse.md +31 -0
  144. spatialflow/_generated/docs/UpdateGeofenceRequest.md +37 -0
  145. spatialflow/_generated/docs/UpdateIntegrationSchema.md +33 -0
  146. spatialflow/_generated/docs/UpdateMemberRoleRequest.md +30 -0
  147. spatialflow/_generated/docs/UpdateOnboardingProgressRequest.md +30 -0
  148. spatialflow/_generated/docs/UpdateProfileRequest.md +45 -0
  149. spatialflow/_generated/docs/UpdateUserWorkspaceRequest.md +31 -0
  150. spatialflow/_generated/docs/UpdateWebhookRequest.md +43 -0
  151. spatialflow/_generated/docs/UploadGeofencesRequest.md +31 -0
  152. spatialflow/_generated/docs/UploadJobStatus.md +42 -0
  153. spatialflow/_generated/docs/UsageMetrics.md +33 -0
  154. spatialflow/_generated/docs/UsageResponse.md +36 -0
  155. spatialflow/_generated/docs/UsageStats.md +32 -0
  156. spatialflow/_generated/docs/UserActionResponse.md +35 -0
  157. spatialflow/_generated/docs/UserApprovalRequest.md +30 -0
  158. spatialflow/_generated/docs/UserInviteRequest.md +33 -0
  159. spatialflow/_generated/docs/UserInviteResponse.md +36 -0
  160. spatialflow/_generated/docs/UserListResponse.md +34 -0
  161. spatialflow/_generated/docs/UserProfileResponse.md +55 -0
  162. spatialflow/_generated/docs/UserRejectionRequest.md +30 -0
  163. spatialflow/_generated/docs/UserResponse.md +44 -0
  164. spatialflow/_generated/docs/UserSummary.md +41 -0
  165. spatialflow/_generated/docs/UserUsageResponse.md +38 -0
  166. spatialflow/_generated/docs/UserWorkspaceResponse.md +34 -0
  167. spatialflow/_generated/docs/WebhookDeliveryDetailResponse.md +49 -0
  168. spatialflow/_generated/docs/WebhookDeliveryListResponse.md +31 -0
  169. spatialflow/_generated/docs/WebhookDeliveryResponse.md +46 -0
  170. spatialflow/_generated/docs/WebhookListResponse.md +31 -0
  171. spatialflow/_generated/docs/WebhookMetricsResponse.md +31 -0
  172. spatialflow/_generated/docs/WebhookResponse.md +50 -0
  173. spatialflow/_generated/docs/WebhookTestResponse.md +36 -0
  174. spatialflow/_generated/docs/WebhooksApi.md +1384 -0
  175. spatialflow/_generated/docs/WorkflowImportSchema.md +30 -0
  176. spatialflow/_generated/docs/WorkflowIn.md +32 -0
  177. spatialflow/_generated/docs/WorkflowListOut.md +39 -0
  178. spatialflow/_generated/docs/WorkflowListResponse.md +32 -0
  179. spatialflow/_generated/docs/WorkflowOut.md +42 -0
  180. spatialflow/_generated/docs/WorkflowRetryPolicyUpdateSchema.md +32 -0
  181. spatialflow/_generated/docs/WorkflowStepRetrySchema.md +33 -0
  182. spatialflow/_generated/docs/WorkflowUpdate.md +33 -0
  183. spatialflow/_generated/docs/WorkflowsApi.md +2599 -0
  184. spatialflow/_generated/docs/WorkspaceDeleteResponse.md +33 -0
  185. spatialflow/_generated/docs/WorkspaceDetail.md +37 -0
  186. spatialflow/_generated/docs/WorkspaceDetailResponse.md +41 -0
  187. spatialflow/_generated/docs/WorkspaceIn.md +34 -0
  188. spatialflow/_generated/docs/WorkspaceListItem.md +39 -0
  189. spatialflow/_generated/docs/WorkspaceListResponse.md +34 -0
  190. spatialflow/_generated/docs/WorkspaceMembersResponse.md +35 -0
  191. spatialflow/_generated/docs/WorkspaceOut.md +38 -0
  192. spatialflow/_generated/docs/WorkspaceSummary.md +32 -0
  193. spatialflow/_generated/docs/WorkspaceUpdateRequest.md +34 -0
  194. spatialflow/_generated/docs/WorkspaceUpdateResponse.md +33 -0
  195. spatialflow/_generated/docs/WorkspacesApi.md +241 -0
  196. spatialflow/_generated/git_push.sh +57 -0
  197. spatialflow/_generated/pyproject.toml +91 -0
  198. spatialflow/_generated/requirements.txt +6 -0
  199. spatialflow/_generated/setup.cfg +2 -0
  200. spatialflow/_generated/setup.py +51 -0
  201. spatialflow/_generated/spatialflow_generated/__init__.py +216 -0
  202. spatialflow/_generated/spatialflow_generated/api/__init__.py +24 -0
  203. spatialflow/_generated/spatialflow_generated/api/account_api.py +5675 -0
  204. spatialflow/_generated/spatialflow_generated/api/admin_api.py +6173 -0
  205. spatialflow/_generated/spatialflow_generated/api/authentication_api.py +4753 -0
  206. spatialflow/_generated/spatialflow_generated/api/billing_api.py +3151 -0
  207. spatialflow/_generated/spatialflow_generated/api/default_api.py +2157 -0
  208. spatialflow/_generated/spatialflow_generated/api/devices_api.py +3965 -0
  209. spatialflow/_generated/spatialflow_generated/api/e2_e_test_api.py +1049 -0
  210. spatialflow/_generated/spatialflow_generated/api/email_api.py +1879 -0
  211. spatialflow/_generated/spatialflow_generated/api/geofences_api.py +4899 -0
  212. spatialflow/_generated/spatialflow_generated/api/gpx_simulator_api.py +2824 -0
  213. spatialflow/_generated/spatialflow_generated/api/integrations_api.py +6952 -0
  214. spatialflow/_generated/spatialflow_generated/api/public_api.py +1506 -0
  215. spatialflow/_generated/spatialflow_generated/api/public_location_ingest_api.py +845 -0
  216. spatialflow/_generated/spatialflow_generated/api/storage_api.py +1642 -0
  217. spatialflow/_generated/spatialflow_generated/api/subscriptions_api.py +2356 -0
  218. spatialflow/_generated/spatialflow_generated/api/system_api.py +529 -0
  219. spatialflow/_generated/spatialflow_generated/api/tiles_api.py +1626 -0
  220. spatialflow/_generated/spatialflow_generated/api/webhooks_api.py +4579 -0
  221. spatialflow/_generated/spatialflow_generated/api/workflows_api.py +8334 -0
  222. spatialflow/_generated/spatialflow_generated/api/workspaces_api.py +813 -0
  223. spatialflow/_generated/spatialflow_generated/api_client.py +800 -0
  224. spatialflow/_generated/spatialflow_generated/api_response.py +21 -0
  225. spatialflow/_generated/spatialflow_generated/configuration.py +599 -0
  226. spatialflow/_generated/spatialflow_generated/exceptions.py +199 -0
  227. spatialflow/_generated/spatialflow_generated/models/__init__.py +180 -0
  228. spatialflow/_generated/spatialflow_generated/models/action_delivery_success_metrics.py +96 -0
  229. spatialflow/_generated/spatialflow_generated/models/action_response.py +96 -0
  230. spatialflow/_generated/spatialflow_generated/models/action_retry_config_schema.py +114 -0
  231. spatialflow/_generated/spatialflow_generated/models/activity_summary.py +89 -0
  232. spatialflow/_generated/spatialflow_generated/models/api_key_create_request.py +95 -0
  233. spatialflow/_generated/spatialflow_generated/models/api_key_create_response.py +89 -0
  234. spatialflow/_generated/spatialflow_generated/models/api_key_response.py +109 -0
  235. spatialflow/_generated/spatialflow_generated/models/api_key_update_request.py +114 -0
  236. spatialflow/_generated/spatialflow_generated/models/api_usage_stats.py +98 -0
  237. spatialflow/_generated/spatialflow_generated/models/async_upload_geofences_response.py +93 -0
  238. spatialflow/_generated/spatialflow_generated/models/auth_type_enum.py +39 -0
  239. spatialflow/_generated/spatialflow_generated/models/batch_location_update_in.py +97 -0
  240. spatialflow/_generated/spatialflow_generated/models/bulk_geofence_request.py +95 -0
  241. spatialflow/_generated/spatialflow_generated/models/change_password_schema.py +89 -0
  242. spatialflow/_generated/spatialflow_generated/models/checkout_session_request.py +91 -0
  243. spatialflow/_generated/spatialflow_generated/models/checkout_session_response.py +89 -0
  244. spatialflow/_generated/spatialflow_generated/models/circuit_breaker_schema.py +94 -0
  245. spatialflow/_generated/spatialflow_generated/models/config_field_definition_request.py +134 -0
  246. spatialflow/_generated/spatialflow_generated/models/config_field_definition_response.py +138 -0
  247. spatialflow/_generated/spatialflow_generated/models/confirm_password_reset_schema.py +89 -0
  248. spatialflow/_generated/spatialflow_generated/models/create_from_template_in.py +94 -0
  249. spatialflow/_generated/spatialflow_generated/models/create_geofence_request.py +125 -0
  250. spatialflow/_generated/spatialflow_generated/models/create_integration_schema.py +106 -0
  251. spatialflow/_generated/spatialflow_generated/models/create_user_schema.py +99 -0
  252. spatialflow/_generated/spatialflow_generated/models/create_webhook_request.py +136 -0
  253. spatialflow/_generated/spatialflow_generated/models/dashboard_comparison_metrics.py +96 -0
  254. spatialflow/_generated/spatialflow_generated/models/dashboard_metrics_response.py +108 -0
  255. spatialflow/_generated/spatialflow_generated/models/delete_file_response.py +89 -0
  256. spatialflow/_generated/spatialflow_generated/models/delivery_status_enum.py +38 -0
  257. spatialflow/_generated/spatialflow_generated/models/device_in.py +98 -0
  258. spatialflow/_generated/spatialflow_generated/models/device_out.py +114 -0
  259. spatialflow/_generated/spatialflow_generated/models/email_health_response.py +92 -0
  260. spatialflow/_generated/spatialflow_generated/models/email_queue_stats.py +97 -0
  261. spatialflow/_generated/spatialflow_generated/models/email_stats.py +91 -0
  262. spatialflow/_generated/spatialflow_generated/models/email_status_response.py +112 -0
  263. spatialflow/_generated/spatialflow_generated/models/error_response.py +101 -0
  264. spatialflow/_generated/spatialflow_generated/models/execution_out.py +126 -0
  265. spatialflow/_generated/spatialflow_generated/models/export_integration_schema.py +97 -0
  266. spatialflow/_generated/spatialflow_generated/models/file_list_response.py +91 -0
  267. spatialflow/_generated/spatialflow_generated/models/forgot_password_schema.py +87 -0
  268. spatialflow/_generated/spatialflow_generated/models/geo_json_point.py +97 -0
  269. spatialflow/_generated/spatialflow_generated/models/geofence_list_response.py +97 -0
  270. spatialflow/_generated/spatialflow_generated/models/geofence_response.py +139 -0
  271. spatialflow/_generated/spatialflow_generated/models/geofence_stats.py +89 -0
  272. spatialflow/_generated/spatialflow_generated/models/geofence_test_result.py +98 -0
  273. spatialflow/_generated/spatialflow_generated/models/gpx_playback_out.py +128 -0
  274. spatialflow/_generated/spatialflow_generated/models/gpx_route_out.py +119 -0
  275. spatialflow/_generated/spatialflow_generated/models/health_check_response.py +91 -0
  276. spatialflow/_generated/spatialflow_generated/models/health_response.py +91 -0
  277. spatialflow/_generated/spatialflow_generated/models/import_integration_schema.py +100 -0
  278. spatialflow/_generated/spatialflow_generated/models/import_result_schema.py +103 -0
  279. spatialflow/_generated/spatialflow_generated/models/integration_detail_schema.py +125 -0
  280. spatialflow/_generated/spatialflow_generated/models/integration_response_schema.py +123 -0
  281. spatialflow/_generated/spatialflow_generated/models/integration_stats_schema.py +116 -0
  282. spatialflow/_generated/spatialflow_generated/models/integration_type_list_response.py +103 -0
  283. spatialflow/_generated/spatialflow_generated/models/integration_type_request.py +117 -0
  284. spatialflow/_generated/spatialflow_generated/models/integration_type_response.py +128 -0
  285. spatialflow/_generated/spatialflow_generated/models/invoice_line_item.py +93 -0
  286. spatialflow/_generated/spatialflow_generated/models/invoice_list_response.py +97 -0
  287. spatialflow/_generated/spatialflow_generated/models/invoice_response.py +120 -0
  288. spatialflow/_generated/spatialflow_generated/models/location_batch_in.py +102 -0
  289. spatialflow/_generated/spatialflow_generated/models/location_import_response.py +120 -0
  290. spatialflow/_generated/spatialflow_generated/models/location_ingest_response.py +117 -0
  291. spatialflow/_generated/spatialflow_generated/models/location_point_in.py +129 -0
  292. spatialflow/_generated/spatialflow_generated/models/location_update_in.py +132 -0
  293. spatialflow/_generated/spatialflow_generated/models/location_update_out.py +93 -0
  294. spatialflow/_generated/spatialflow_generated/models/login_response.py +95 -0
  295. spatialflow/_generated/spatialflow_generated/models/login_schema.py +89 -0
  296. spatialflow/_generated/spatialflow_generated/models/member_action_response.py +98 -0
  297. spatialflow/_generated/spatialflow_generated/models/member_summary.py +114 -0
  298. spatialflow/_generated/spatialflow_generated/models/method_enum.py +39 -0
  299. spatialflow/_generated/spatialflow_generated/models/o_auth_authorize_response.py +87 -0
  300. spatialflow/_generated/spatialflow_generated/models/o_auth_callback_query.py +103 -0
  301. spatialflow/_generated/spatialflow_generated/models/o_auth_link_response.py +91 -0
  302. spatialflow/_generated/spatialflow_generated/models/o_auth_providers_response.py +87 -0
  303. spatialflow/_generated/spatialflow_generated/models/onboarding_progress_response.py +112 -0
  304. spatialflow/_generated/spatialflow_generated/models/payment_method_response.py +116 -0
  305. spatialflow/_generated/spatialflow_generated/models/ping_response.py +91 -0
  306. spatialflow/_generated/spatialflow_generated/models/plan_change_preview_response.py +104 -0
  307. spatialflow/_generated/spatialflow_generated/models/plan_features.py +109 -0
  308. spatialflow/_generated/spatialflow_generated/models/plan_limits.py +95 -0
  309. spatialflow/_generated/spatialflow_generated/models/plan_response.py +114 -0
  310. spatialflow/_generated/spatialflow_generated/models/portal_session_request.py +87 -0
  311. spatialflow/_generated/spatialflow_generated/models/portal_session_response.py +87 -0
  312. spatialflow/_generated/spatialflow_generated/models/presigned_url_request.py +91 -0
  313. spatialflow/_generated/spatialflow_generated/models/presigned_url_response.py +99 -0
  314. spatialflow/_generated/spatialflow_generated/models/privacy_erasure_request.py +118 -0
  315. spatialflow/_generated/spatialflow_generated/models/privacy_erasure_response.py +110 -0
  316. spatialflow/_generated/spatialflow_generated/models/rate_limit_response.py +91 -0
  317. spatialflow/_generated/spatialflow_generated/models/recent_activity.py +91 -0
  318. spatialflow/_generated/spatialflow_generated/models/refresh_token_schema.py +87 -0
  319. spatialflow/_generated/spatialflow_generated/models/register_schema.py +133 -0
  320. spatialflow/_generated/spatialflow_generated/models/resend_verification_schema.py +87 -0
  321. spatialflow/_generated/spatialflow_generated/models/reset_password_schema.py +91 -0
  322. spatialflow/_generated/spatialflow_generated/models/retry_policy_response_schema.py +95 -0
  323. spatialflow/_generated/spatialflow_generated/models/retry_policy_schema.py +99 -0
  324. spatialflow/_generated/spatialflow_generated/models/retry_strategy_enum.py +38 -0
  325. spatialflow/_generated/spatialflow_generated/models/seed_data_response_schema.py +91 -0
  326. spatialflow/_generated/spatialflow_generated/models/send_email_request.py +100 -0
  327. spatialflow/_generated/spatialflow_generated/models/setup_intent_response.py +87 -0
  328. spatialflow/_generated/spatialflow_generated/models/signup_request.py +164 -0
  329. spatialflow/_generated/spatialflow_generated/models/start_playback_request.py +89 -0
  330. spatialflow/_generated/spatialflow_generated/models/subscription_action_response.py +100 -0
  331. spatialflow/_generated/spatialflow_generated/models/subscription_response.py +113 -0
  332. spatialflow/_generated/spatialflow_generated/models/success_response.py +94 -0
  333. spatialflow/_generated/spatialflow_generated/models/template_out.py +104 -0
  334. spatialflow/_generated/spatialflow_generated/models/test_event_request.py +92 -0
  335. spatialflow/_generated/spatialflow_generated/models/test_integration_response_schema.py +91 -0
  336. spatialflow/_generated/spatialflow_generated/models/test_point_request.py +125 -0
  337. spatialflow/_generated/spatialflow_generated/models/test_point_response.py +101 -0
  338. spatialflow/_generated/spatialflow_generated/models/test_webhook_request.py +106 -0
  339. spatialflow/_generated/spatialflow_generated/models/test_workflow_in.py +87 -0
  340. spatialflow/_generated/spatialflow_generated/models/tile_metadata.py +99 -0
  341. spatialflow/_generated/spatialflow_generated/models/unsubscribe_request.py +87 -0
  342. spatialflow/_generated/spatialflow_generated/models/unsubscribe_response.py +91 -0
  343. spatialflow/_generated/spatialflow_generated/models/update_geofence_request.py +142 -0
  344. spatialflow/_generated/spatialflow_generated/models/update_integration_schema.py +121 -0
  345. spatialflow/_generated/spatialflow_generated/models/update_member_role_request.py +87 -0
  346. spatialflow/_generated/spatialflow_generated/models/update_onboarding_progress_request.py +87 -0
  347. spatialflow/_generated/spatialflow_generated/models/update_profile_request.py +208 -0
  348. spatialflow/_generated/spatialflow_generated/models/update_user_workspace_request.py +89 -0
  349. spatialflow/_generated/spatialflow_generated/models/update_webhook_request.py +186 -0
  350. spatialflow/_generated/spatialflow_generated/models/upload_geofences_request.py +95 -0
  351. spatialflow/_generated/spatialflow_generated/models/upload_job_status.py +137 -0
  352. spatialflow/_generated/spatialflow_generated/models/usage_metrics.py +93 -0
  353. spatialflow/_generated/spatialflow_generated/models/usage_response.py +105 -0
  354. spatialflow/_generated/spatialflow_generated/models/usage_stats.py +91 -0
  355. spatialflow/_generated/spatialflow_generated/models/user_action_response.py +119 -0
  356. spatialflow/_generated/spatialflow_generated/models/user_approval_request.py +92 -0
  357. spatialflow/_generated/spatialflow_generated/models/user_invite_request.py +110 -0
  358. spatialflow/_generated/spatialflow_generated/models/user_invite_response.py +99 -0
  359. spatialflow/_generated/spatialflow_generated/models/user_list_response.py +110 -0
  360. spatialflow/_generated/spatialflow_generated/models/user_profile_response.py +158 -0
  361. spatialflow/_generated/spatialflow_generated/models/user_rejection_request.py +87 -0
  362. spatialflow/_generated/spatialflow_generated/models/user_response.py +142 -0
  363. spatialflow/_generated/spatialflow_generated/models/user_summary.py +140 -0
  364. spatialflow/_generated/spatialflow_generated/models/user_usage_response.py +152 -0
  365. spatialflow/_generated/spatialflow_generated/models/user_workspace_response.py +110 -0
  366. spatialflow/_generated/spatialflow_generated/models/webhook_delivery_detail_response.py +182 -0
  367. spatialflow/_generated/spatialflow_generated/models/webhook_delivery_list_response.py +97 -0
  368. spatialflow/_generated/spatialflow_generated/models/webhook_delivery_response.py +161 -0
  369. spatialflow/_generated/spatialflow_generated/models/webhook_list_response.py +97 -0
  370. spatialflow/_generated/spatialflow_generated/models/webhook_metrics_response.py +89 -0
  371. spatialflow/_generated/spatialflow_generated/models/webhook_response.py +148 -0
  372. spatialflow/_generated/spatialflow_generated/models/webhook_test_response.py +114 -0
  373. spatialflow/_generated/spatialflow_generated/models/workflow_import_schema.py +89 -0
  374. spatialflow/_generated/spatialflow_generated/models/workflow_in.py +98 -0
  375. spatialflow/_generated/spatialflow_generated/models/workflow_list_out.py +118 -0
  376. spatialflow/_generated/spatialflow_generated/models/workflow_list_response.py +101 -0
  377. spatialflow/_generated/spatialflow_generated/models/workflow_out.py +129 -0
  378. spatialflow/_generated/spatialflow_generated/models/workflow_retry_policy_update_schema.py +109 -0
  379. spatialflow/_generated/spatialflow_generated/models/workflow_step_retry_schema.py +103 -0
  380. spatialflow/_generated/spatialflow_generated/models/workflow_update.py +120 -0
  381. spatialflow/_generated/spatialflow_generated/models/workspace_delete_response.py +93 -0
  382. spatialflow/_generated/spatialflow_generated/models/workspace_detail.py +116 -0
  383. spatialflow/_generated/spatialflow_generated/models/workspace_detail_response.py +143 -0
  384. spatialflow/_generated/spatialflow_generated/models/workspace_in.py +122 -0
  385. spatialflow/_generated/spatialflow_generated/models/workspace_list_item.py +130 -0
  386. spatialflow/_generated/spatialflow_generated/models/workspace_list_response.py +103 -0
  387. spatialflow/_generated/spatialflow_generated/models/workspace_members_response.py +109 -0
  388. spatialflow/_generated/spatialflow_generated/models/workspace_out.py +132 -0
  389. spatialflow/_generated/spatialflow_generated/models/workspace_summary.py +91 -0
  390. spatialflow/_generated/spatialflow_generated/models/workspace_update_request.py +120 -0
  391. spatialflow/_generated/spatialflow_generated/models/workspace_update_response.py +93 -0
  392. spatialflow/_generated/spatialflow_generated/py.typed +0 -0
  393. spatialflow/_generated/spatialflow_generated/rest.py +215 -0
  394. spatialflow/_generated/test-requirements.txt +6 -0
  395. spatialflow/_generated/tox.ini +9 -0
  396. spatialflow/client.py +137 -0
  397. spatialflow/exceptions.py +234 -0
  398. spatialflow/jobs.py +204 -0
  399. spatialflow/pagination.py +142 -0
  400. spatialflow/uploads.py +166 -0
  401. spatialflow/webhooks.py +126 -0
  402. spatialflow-0.1.0.dist-info/METADATA +249 -0
  403. spatialflow-0.1.0.dist-info/RECORD +404 -0
  404. spatialflow-0.1.0.dist-info/WHEEL +4 -0
spatialflow/jobs.py ADDED
@@ -0,0 +1,204 @@
1
+ """
2
+ Async job polling helpers for SpatialFlow SDK.
3
+
4
+ Provides utilities for polling long-running jobs like file uploads.
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Any, Callable, Dict, Optional, TypeVar
9
+
10
+ from .exceptions import SpatialFlowError, TimeoutError
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class JobTimeoutError(TimeoutError):
16
+ """Raised when a job polling operation times out."""
17
+
18
+ def __init__(self, job_id: str, timeout: float, last_status: Optional[str] = None):
19
+ self.job_id = job_id
20
+ self.timeout = timeout
21
+ self.last_status = last_status
22
+ message = f"Job {job_id} did not complete within {timeout} seconds"
23
+ if last_status:
24
+ message += f" (last status: {last_status})"
25
+ super().__init__(message)
26
+
27
+
28
+ class JobFailedError(SpatialFlowError):
29
+ """Raised when a polled job fails."""
30
+
31
+ def __init__(
32
+ self,
33
+ job_id: str,
34
+ error_message: Optional[str] = None,
35
+ results: Optional[Dict[str, Any]] = None,
36
+ ):
37
+ self.job_id = job_id
38
+ self.error_message = error_message
39
+ self.results = results
40
+ message = f"Job {job_id} failed"
41
+ if error_message:
42
+ message += f": {error_message}"
43
+ super().__init__(message)
44
+
45
+
46
+ class JobResult:
47
+ """Result of a completed job."""
48
+
49
+ def __init__(
50
+ self,
51
+ job_id: str,
52
+ status: str,
53
+ created_count: int = 0,
54
+ failed_count: int = 0,
55
+ total_features: int = 0,
56
+ results: Optional[Dict[str, Any]] = None,
57
+ duration: Optional[float] = None,
58
+ raw_response: Optional[Any] = None,
59
+ ):
60
+ self.job_id = job_id
61
+ self.status = status
62
+ self.created_count = created_count
63
+ self.failed_count = failed_count
64
+ self.total_features = total_features
65
+ self.results = results or {}
66
+ self.duration = duration
67
+ self.raw_response = raw_response
68
+
69
+ @property
70
+ def created_geofences(self) -> list:
71
+ """List of created geofence info (id, name)."""
72
+ return self.results.get("created_geofences", [])
73
+
74
+ @property
75
+ def errors(self) -> list:
76
+ """List of errors that occurred during processing."""
77
+ return self.results.get("errors", [])
78
+
79
+ @property
80
+ def warnings(self) -> list:
81
+ """List of warnings from processing."""
82
+ return self.results.get("warnings", [])
83
+
84
+ def __repr__(self) -> str:
85
+ return (
86
+ f"JobResult(job_id={self.job_id!r}, status={self.status!r}, "
87
+ f"created={self.created_count}, failed={self.failed_count})"
88
+ )
89
+
90
+
91
+ async def poll_job(
92
+ fetch_status: Callable[[], Any],
93
+ *,
94
+ timeout: float = 300,
95
+ poll_interval: float = 2.0,
96
+ terminal_statuses: tuple = ("completed", "failed"),
97
+ on_status: Optional[Callable[[str, Any], None]] = None,
98
+ extract_job_id: Optional[Callable[[Any], str]] = None,
99
+ extract_status: Optional[Callable[[Any], str]] = None,
100
+ ) -> JobResult:
101
+ """
102
+ Poll a job until it reaches a terminal status.
103
+
104
+ Args:
105
+ fetch_status: Async function that fetches the current job status.
106
+ Should return a response object with status information.
107
+ timeout: Maximum time to wait in seconds. Default: 300 (5 minutes).
108
+ poll_interval: Time between polls in seconds. Default: 2.0.
109
+ terminal_statuses: Tuple of statuses that indicate the job is done.
110
+ Default: ("completed", "failed").
111
+ on_status: Optional callback called on each poll with (status, response).
112
+ extract_job_id: Function to extract job_id from response.
113
+ Default: looks for job_id attribute or dict key.
114
+ extract_status: Function to extract status from response.
115
+ Default: looks for status attribute or dict key.
116
+
117
+ Returns:
118
+ JobResult with the final job state.
119
+
120
+ Raises:
121
+ JobTimeoutError: If the job doesn't complete within the timeout.
122
+ JobFailedError: If the job fails.
123
+
124
+ Example:
125
+ >>> async def get_status():
126
+ ... return await client.geofences.apps_geofences_api_get_upload_job_status(
127
+ ... job_id=job_id
128
+ ... )
129
+ ...
130
+ >>> result = await poll_job(get_status, timeout=120)
131
+ >>> print(f"Created {result.created_count} geofences")
132
+ """
133
+ extract_job_id = extract_job_id or _default_extract_job_id
134
+ extract_status = extract_status or _default_extract_status
135
+
136
+ elapsed = 0.0
137
+ last_status: Optional[str] = None
138
+ last_response: Optional[Any] = None
139
+
140
+ while elapsed < timeout:
141
+ response = await fetch_status()
142
+ last_response = response
143
+ status = extract_status(response)
144
+ last_status = status
145
+
146
+ if on_status:
147
+ on_status(status, response)
148
+
149
+ if status in terminal_statuses:
150
+ job_id = extract_job_id(response)
151
+ return _build_job_result(job_id, status, response)
152
+
153
+ await asyncio.sleep(poll_interval)
154
+ elapsed += poll_interval
155
+
156
+ # Timeout
157
+ job_id = extract_job_id(last_response) if last_response else "unknown"
158
+ raise JobTimeoutError(job_id, timeout, last_status)
159
+
160
+
161
+ def _default_extract_job_id(response: Any) -> str:
162
+ """Extract job_id from response."""
163
+ if hasattr(response, "job_id"):
164
+ return str(response.job_id)
165
+ if isinstance(response, dict):
166
+ return str(response.get("job_id", "unknown"))
167
+ return "unknown"
168
+
169
+
170
+ def _default_extract_status(response: Any) -> str:
171
+ """Extract status from response."""
172
+ if hasattr(response, "status"):
173
+ return str(response.status)
174
+ if isinstance(response, dict):
175
+ return str(response.get("status", "unknown"))
176
+ return "unknown"
177
+
178
+
179
+ def _build_job_result(job_id: str, status: str, response: Any) -> JobResult:
180
+ """Build a JobResult from the response."""
181
+ # Handle both object attributes and dict access
182
+ def get_field(name: str, default: Any = None) -> Any:
183
+ if hasattr(response, name):
184
+ return getattr(response, name)
185
+ if isinstance(response, dict):
186
+ return response.get(name, default)
187
+ return default
188
+
189
+ # Check for failure
190
+ if status == "failed":
191
+ error_message = get_field("error_message")
192
+ results = get_field("results")
193
+ raise JobFailedError(job_id, error_message, results)
194
+
195
+ return JobResult(
196
+ job_id=job_id,
197
+ status=status,
198
+ created_count=get_field("created_count", 0),
199
+ failed_count=get_field("failed_count", 0),
200
+ total_features=get_field("total_features", 0),
201
+ results=get_field("results"),
202
+ duration=get_field("duration"),
203
+ raw_response=response,
204
+ )
@@ -0,0 +1,142 @@
1
+ """
2
+ Pagination helpers for SpatialFlow SDK.
3
+
4
+ Provides async iterators for paginated API responses.
5
+ """
6
+
7
+ from typing import TypeVar, Generic, AsyncIterator, Callable, Awaitable, Any, List
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ class PaginatedResponse(Generic[T]):
13
+ """
14
+ A paginated response with helper methods for navigation.
15
+
16
+ Attributes:
17
+ items: The items in the current page
18
+ count: Total number of items across all pages
19
+ next_url: URL for the next page (if any)
20
+ previous_url: URL for the previous page (if any)
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ items: List[T],
26
+ count: int,
27
+ next_url: str | None = None,
28
+ previous_url: str | None = None,
29
+ ):
30
+ self.items = items
31
+ self.count = count
32
+ self.next_url = next_url
33
+ self.previous_url = previous_url
34
+
35
+ @property
36
+ def has_more(self) -> bool:
37
+ """Returns True if there are more pages available."""
38
+ return self.next_url is not None
39
+
40
+ def __iter__(self):
41
+ """Iterate over items in the current page."""
42
+ return iter(self.items)
43
+
44
+ def __len__(self) -> int:
45
+ """Number of items in the current page."""
46
+ return len(self.items)
47
+
48
+
49
+ class AsyncPaginator(Generic[T]):
50
+ """
51
+ Async iterator for paginated API responses.
52
+
53
+ Automatically fetches subsequent pages as you iterate.
54
+
55
+ Example:
56
+ >>> async for geofence in client.geofences.list_all():
57
+ ... print(geofence.name)
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ fetch_page: Callable[[int, int], Awaitable[Any]],
63
+ extract_items: Callable[[Any], List[T]],
64
+ extract_count: Callable[[Any], int],
65
+ extract_next: Callable[[Any], str | None],
66
+ limit: int = 100,
67
+ ):
68
+ """
69
+ Initialize the paginator.
70
+
71
+ Args:
72
+ fetch_page: Async function that fetches a page (offset, limit) -> response
73
+ extract_items: Function to extract items list from response
74
+ extract_count: Function to extract total count from response
75
+ extract_next: Function to extract next page URL from response
76
+ limit: Number of items per page
77
+ """
78
+ self._fetch_page = fetch_page
79
+ self._extract_items = extract_items
80
+ self._extract_count = extract_count
81
+ self._extract_next = extract_next
82
+ self._limit = limit
83
+ self._offset = 0
84
+ self._exhausted = False
85
+ self._total_count: int | None = None
86
+
87
+ async def __aiter__(self) -> AsyncIterator[T]:
88
+ """Async iterate over all items across all pages."""
89
+ while not self._exhausted:
90
+ response = await self._fetch_page(self._offset, self._limit)
91
+ items = self._extract_items(response)
92
+ self._total_count = self._extract_count(response)
93
+ next_url = self._extract_next(response)
94
+
95
+ for item in items:
96
+ yield item
97
+
98
+ if next_url is None or len(items) < self._limit:
99
+ self._exhausted = True
100
+ else:
101
+ self._offset += self._limit
102
+
103
+ @property
104
+ def total_count(self) -> int | None:
105
+ """
106
+ Total count of items (available after first page is fetched).
107
+ """
108
+ return self._total_count
109
+
110
+
111
+ def paginate(
112
+ fetch_page: Callable[[int, int], Awaitable[Any]],
113
+ limit: int = 100,
114
+ ) -> AsyncPaginator:
115
+ """
116
+ Create a paginator for a list endpoint.
117
+
118
+ This is a convenience function for the common case where the response
119
+ has 'results', 'count', and 'next' fields.
120
+
121
+ Args:
122
+ fetch_page: Async function that takes (offset, limit) and returns response
123
+ limit: Number of items per page (default 100)
124
+
125
+ Returns:
126
+ AsyncPaginator that yields items from all pages
127
+
128
+ Example:
129
+ >>> async def fetch(offset, limit):
130
+ ... return await client.geofences.apps_geofences_api_list_geofences(
131
+ ... offset=offset, limit=limit
132
+ ... )
133
+ >>> async for geofence in paginate(fetch):
134
+ ... print(geofence.name)
135
+ """
136
+ return AsyncPaginator(
137
+ fetch_page=fetch_page,
138
+ extract_items=lambda r: r.results if hasattr(r, "results") else [],
139
+ extract_count=lambda r: r.count if hasattr(r, "count") else 0,
140
+ extract_next=lambda r: r.next if hasattr(r, "next") else None,
141
+ limit=limit,
142
+ )
spatialflow/uploads.py ADDED
@@ -0,0 +1,166 @@
1
+ """
2
+ File upload helpers for SpatialFlow SDK.
3
+
4
+ Provides utilities for uploading files (GeoJSON, KML, GPX) and processing them.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Any, Callable, Optional, Union
10
+
11
+ import aiohttp
12
+
13
+ from .jobs import JobResult, poll_job
14
+
15
+
16
+ async def upload_geofences(
17
+ client: Any,
18
+ file_path: Union[str, Path],
19
+ *,
20
+ group_name: Optional[str] = None,
21
+ timeout: float = 300,
22
+ poll_interval: float = 2.0,
23
+ on_status: Optional[Callable[[str, Any], None]] = None,
24
+ ) -> JobResult:
25
+ """
26
+ Upload a geofence file and wait for processing to complete.
27
+
28
+ This is a convenience method that:
29
+ 1. Requests a presigned upload URL
30
+ 2. Uploads the file to S3
31
+ 3. Starts the geofence import job
32
+ 4. Polls until completion
33
+
34
+ Args:
35
+ client: SpatialFlow client instance.
36
+ file_path: Path to the file to upload (GeoJSON, KML, or GPX).
37
+ group_name: Optional name for the geofence group.
38
+ timeout: Maximum time to wait for processing in seconds. Default: 300.
39
+ poll_interval: Time between status polls in seconds. Default: 2.0.
40
+ on_status: Optional callback called on each poll with (status, response).
41
+
42
+ Returns:
43
+ JobResult with the final job state including created geofences.
44
+
45
+ Raises:
46
+ FileNotFoundError: If the file doesn't exist.
47
+ ValueError: If the file type is not supported.
48
+ JobTimeoutError: If processing doesn't complete within timeout.
49
+ JobFailedError: If the import job fails.
50
+
51
+ Example:
52
+ >>> result = await upload_geofences(
53
+ ... client,
54
+ ... "boundaries.geojson",
55
+ ... group_name="my-region",
56
+ ... timeout=120,
57
+ ... )
58
+ >>> print(f"Created {result.created_count} geofences")
59
+ """
60
+ file_path = Path(file_path)
61
+
62
+ if not file_path.exists():
63
+ raise FileNotFoundError(f"File not found: {file_path}")
64
+
65
+ # Get file info
66
+ filename = file_path.name
67
+ file_size = file_path.stat().st_size
68
+
69
+ # Determine content type
70
+ ext = file_path.suffix.lower()
71
+ content_types = {
72
+ ".geojson": "application/geo+json",
73
+ ".json": "application/json",
74
+ ".kml": "application/vnd.google-earth.kml+xml",
75
+ ".gpx": "application/gpx+xml",
76
+ }
77
+
78
+ if ext not in content_types:
79
+ raise ValueError(
80
+ f"Unsupported file type: {ext}. "
81
+ f"Supported types: {', '.join(content_types.keys())}"
82
+ )
83
+
84
+ content_type = content_types[ext]
85
+
86
+ # Step 1: Get presigned upload URL
87
+ presigned_response = await client.storage.apps_storage_api_create_presigned_url(
88
+ presigned_url_request={
89
+ "file_type": "geofences",
90
+ "filename": filename,
91
+ "file_size": file_size,
92
+ }
93
+ )
94
+
95
+ # Extract from response (handle both direct and axios-style responses)
96
+ presigned_data = _extract_data(presigned_response)
97
+ upload_url = presigned_data.get("upload_url")
98
+ file_id = presigned_data.get("file_id")
99
+
100
+ if not upload_url:
101
+ raise ValueError(
102
+ "Failed to get presigned upload URL from storage API. "
103
+ "Response missing 'upload_url' field."
104
+ )
105
+ if not file_id:
106
+ raise ValueError(
107
+ "Failed to get file ID from storage API. "
108
+ "Response missing 'file_id' field."
109
+ )
110
+
111
+ # Step 2: Upload file to S3
112
+ # Note: For large files, consider using aiohttp's streaming upload.
113
+ # This implementation reads the entire file into memory for simplicity.
114
+ async with aiohttp.ClientSession() as session:
115
+ with open(file_path, "rb") as f:
116
+ file_content = f.read()
117
+
118
+ async with session.put(
119
+ upload_url,
120
+ data=file_content,
121
+ headers={"Content-Type": content_type},
122
+ ) as resp:
123
+ if resp.status not in (200, 204):
124
+ text = await resp.text()
125
+ raise ValueError(f"Failed to upload file to S3: {resp.status} - {text}")
126
+
127
+ # Step 3: Start the geofence import job
128
+ upload_request = {"file_id": file_id}
129
+ if group_name:
130
+ upload_request["group_name"] = group_name
131
+
132
+ job_response = await client.geofences.apps_geofences_api_upload_geofences_async(
133
+ upload_geofences_request=upload_request
134
+ )
135
+
136
+ job_data = _extract_data(job_response)
137
+ job_id = job_data.get("job_id")
138
+
139
+ if not job_id:
140
+ raise ValueError(
141
+ "Failed to start upload job. Response missing 'job_id' field."
142
+ )
143
+
144
+ # Step 4: Poll for completion
145
+ async def fetch_status():
146
+ return await client.geofences.apps_geofences_api_get_upload_job_status(
147
+ job_id=job_id
148
+ )
149
+
150
+ return await poll_job(
151
+ fetch_status,
152
+ timeout=timeout,
153
+ poll_interval=poll_interval,
154
+ on_status=on_status,
155
+ )
156
+
157
+
158
+ def _extract_data(response: Any) -> dict:
159
+ """Extract data from response, handling axios-style responses."""
160
+ if hasattr(response, "data"):
161
+ return response.data if isinstance(response.data, dict) else response.data.__dict__
162
+ if isinstance(response, dict):
163
+ return response
164
+ if hasattr(response, "__dict__"):
165
+ return response.__dict__
166
+ return {}
@@ -0,0 +1,126 @@
1
+ """
2
+ Webhook signature verification for SpatialFlow SDK.
3
+
4
+ Provides HMAC-SHA256 signature verification for webhook payloads.
5
+ """
6
+
7
+ import hashlib
8
+ import hmac
9
+ import time
10
+ from typing import Union
11
+
12
+ from .exceptions import SpatialFlowError
13
+
14
+
15
+ class WebhookSignatureError(SpatialFlowError):
16
+ """Raised when webhook signature verification fails."""
17
+
18
+ pass
19
+
20
+
21
+ def verify_webhook_signature(
22
+ payload: Union[str, bytes],
23
+ signature: str,
24
+ secret: str,
25
+ tolerance: int = 300,
26
+ ) -> dict:
27
+ """
28
+ Verify a webhook signature and return the parsed payload.
29
+
30
+ SpatialFlow webhooks include an HMAC-SHA256 signature in the
31
+ `X-SF-Signature` header. This function verifies that signature
32
+ and optionally checks the timestamp to prevent replay attacks.
33
+
34
+ Args:
35
+ payload: The raw webhook payload (request body as string or bytes)
36
+ signature: The signature from X-SF-Signature header
37
+ secret: Your webhook signing secret
38
+ tolerance: Maximum age of the webhook in seconds (default 300 = 5 minutes).
39
+ Set to 0 to disable timestamp checking.
40
+
41
+ Returns:
42
+ The parsed webhook payload as a dict
43
+
44
+ Raises:
45
+ WebhookSignatureError: If signature is invalid or timestamp is too old
46
+
47
+ Example:
48
+ >>> from spatialflow import verify_webhook_signature
49
+ >>>
50
+ >>> # In your webhook handler (e.g., Flask/FastAPI)
51
+ >>> @app.post("/webhook")
52
+ >>> async def handle_webhook(request):
53
+ ... payload = await request.body()
54
+ ... signature = request.headers.get("X-SF-Signature")
55
+ ...
56
+ ... try:
57
+ ... event = verify_webhook_signature(
58
+ ... payload=payload,
59
+ ... signature=signature,
60
+ ... secret=WEBHOOK_SECRET,
61
+ ... )
62
+ ... # Process the verified event
63
+ ... print(f"Event type: {event['type']}")
64
+ ... return {"status": "ok"}
65
+ ... except WebhookSignatureError as e:
66
+ ... return {"error": str(e)}, 400
67
+ """
68
+ import json
69
+
70
+ # Normalize payload to bytes
71
+ if isinstance(payload, str):
72
+ payload_bytes = payload.encode("utf-8")
73
+ else:
74
+ payload_bytes = payload
75
+
76
+ # Parse the signature header
77
+ # Format: t=<timestamp>,v1=<signature>
78
+ try:
79
+ parts = dict(part.split("=", 1) for part in signature.split(","))
80
+ timestamp_str = parts.get("t")
81
+ sig_hash = parts.get("v1")
82
+
83
+ if not timestamp_str or not sig_hash:
84
+ raise WebhookSignatureError(
85
+ "Invalid signature format. Expected: t=<timestamp>,v1=<signature>"
86
+ )
87
+
88
+ timestamp = int(timestamp_str)
89
+ except (ValueError, AttributeError) as e:
90
+ raise WebhookSignatureError(f"Failed to parse signature header: {e}")
91
+
92
+ # Check timestamp tolerance (replay attack prevention)
93
+ if tolerance > 0:
94
+ now = int(time.time())
95
+ age = now - timestamp
96
+ if age > tolerance:
97
+ raise WebhookSignatureError(
98
+ f"Webhook timestamp too old: {age}s (tolerance: {tolerance}s)"
99
+ )
100
+ if age < -tolerance:
101
+ raise WebhookSignatureError(
102
+ f"Webhook timestamp in future: {-age}s (tolerance: {tolerance}s)"
103
+ )
104
+
105
+ # Compute expected signature
106
+ # The signed payload is: timestamp.payload
107
+ signed_payload = f"{timestamp}.".encode("utf-8") + payload_bytes
108
+ expected_sig = hmac.new(
109
+ secret.encode("utf-8"),
110
+ signed_payload,
111
+ hashlib.sha256,
112
+ ).hexdigest()
113
+
114
+ # Constant-time comparison to prevent timing attacks
115
+ if not hmac.compare_digest(expected_sig, sig_hash):
116
+ raise WebhookSignatureError("Signature verification failed")
117
+
118
+ # Parse and return the payload
119
+ try:
120
+ return json.loads(payload_bytes.decode("utf-8"))
121
+ except json.JSONDecodeError as e:
122
+ raise WebhookSignatureError(f"Failed to parse webhook payload as JSON: {e}")
123
+
124
+
125
+ # Convenience alias
126
+ verify_signature = verify_webhook_signature