b1sl-python 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 (579) hide show
  1. b1sl/__init__.py +5 -0
  2. b1sl/b1sl/__init__.py +56 -0
  3. b1sl/b1sl/_types.py +28 -0
  4. b1sl/b1sl/adapter.py +40 -0
  5. b1sl/b1sl/adapter_protocol.py +28 -0
  6. b1sl/b1sl/async_client.py +268 -0
  7. b1sl/b1sl/async_rest_adapter.py +347 -0
  8. b1sl/b1sl/base_adapter.py +394 -0
  9. b1sl/b1sl/client.py +112 -0
  10. b1sl/b1sl/config.py +138 -0
  11. b1sl/b1sl/config_manager.py +77 -0
  12. b1sl/b1sl/contrib/__init__.py +14 -0
  13. b1sl/b1sl/contrib/django/__init__.py +13 -0
  14. b1sl/b1sl/contrib/django/middleware/__init__.py +27 -0
  15. b1sl/b1sl/contrib/django/middleware/base.py +89 -0
  16. b1sl/b1sl/contrib/django/middleware/odata_decode_middleware.py +20 -0
  17. b1sl/b1sl/contrib/django/middleware/odata_transform_url_middleware.py +20 -0
  18. b1sl/b1sl/entities/__init__.py +2209 -0
  19. b1sl/b1sl/environment.py +36 -0
  20. b1sl/b1sl/exceptions/exceptions.py +142 -0
  21. b1sl/b1sl/fields/__init__.py +2020 -0
  22. b1sl/b1sl/fields/_generated/__init__.py +0 -0
  23. b1sl/b1sl/fields/_generated/complex_types.py +6926 -0
  24. b1sl/b1sl/fields/_generated/entities/__init__.py +0 -0
  25. b1sl/b1sl/fields/_generated/entities/businesspartners.py +544 -0
  26. b1sl/b1sl/fields/_generated/entities/finance.py +451 -0
  27. b1sl/b1sl/fields/_generated/entities/general.py +4587 -0
  28. b1sl/b1sl/fields/_generated/entities/inventory.py +761 -0
  29. b1sl/b1sl/fields/_generated/entities/production.py +256 -0
  30. b1sl/b1sl/fields/_generated/entities/purchasing.py +53 -0
  31. b1sl/b1sl/fields/_generated/entities/sales.py +244 -0
  32. b1sl/b1sl/logging_utils.py +119 -0
  33. b1sl/b1sl/models/__init__.py +13 -0
  34. b1sl/b1sl/models/_generated/__init__.py +0 -0
  35. b1sl/b1sl/models/_generated/_types.py +5 -0
  36. b1sl/b1sl/models/_generated/complex_types.py +6929 -0
  37. b1sl/b1sl/models/_generated/entities/__init__.py +581 -0
  38. b1sl/b1sl/models/_generated/entities/businesspartners.py +565 -0
  39. b1sl/b1sl/models/_generated/entities/finance.py +472 -0
  40. b1sl/b1sl/models/_generated/entities/general.py +4831 -0
  41. b1sl/b1sl/models/_generated/entities/inventory.py +786 -0
  42. b1sl/b1sl/models/_generated/entities/production.py +272 -0
  43. b1sl/b1sl/models/_generated/entities/purchasing.py +64 -0
  44. b1sl/b1sl/models/_generated/entities/sales.py +263 -0
  45. b1sl/b1sl/models/_generated/enums.py +3501 -0
  46. b1sl/b1sl/models/_generated/resources/__init__.py +0 -0
  47. b1sl/b1sl/models/_overrides/inventory.py +24 -0
  48. b1sl/b1sl/models/base.py +209 -0
  49. b1sl/b1sl/models/odata_query_model.py +50 -0
  50. b1sl/b1sl/models/paginated_result.py +85 -0
  51. b1sl/b1sl/models/result.py +17 -0
  52. b1sl/b1sl/resources/__init__.py +11 -0
  53. b1sl/b1sl/resources/_generated/__init__.py +0 -0
  54. b1sl/b1sl/resources/_generated/account_category.py +15 -0
  55. b1sl/b1sl/resources/_generated/account_category_service.py +21 -0
  56. b1sl/b1sl/resources/_generated/account_segmentation_categories.py +15 -0
  57. b1sl/b1sl/resources/_generated/account_segmentations.py +15 -0
  58. b1sl/b1sl/resources/_generated/accounts_service.py +33 -0
  59. b1sl/b1sl/resources/_generated/accrual_types.py +15 -0
  60. b1sl/b1sl/resources/_generated/accrual_types_service.py +21 -0
  61. b1sl/b1sl/resources/_generated/activities.py +15 -0
  62. b1sl/b1sl/resources/_generated/activities_service.py +109 -0
  63. b1sl/b1sl/resources/_generated/activity_locations.py +15 -0
  64. b1sl/b1sl/resources/_generated/activity_recipient_lists.py +15 -0
  65. b1sl/b1sl/resources/_generated/activity_recipient_lists_service.py +21 -0
  66. b1sl/b1sl/resources/_generated/activity_statuses.py +15 -0
  67. b1sl/b1sl/resources/_generated/activity_types.py +15 -0
  68. b1sl/b1sl/resources/_generated/additional_expenses.py +15 -0
  69. b1sl/b1sl/resources/_generated/address_service.py +38 -0
  70. b1sl/b1sl/resources/_generated/alert_managements.py +16 -0
  71. b1sl/b1sl/resources/_generated/alternate_cat_num.py +15 -0
  72. b1sl/b1sl/resources/_generated/alternative_items_service.py +113 -0
  73. b1sl/b1sl/resources/_generated/approval_requests.py +15 -0
  74. b1sl/b1sl/resources/_generated/approval_requests_service.py +33 -0
  75. b1sl/b1sl/resources/_generated/approval_stages.py +15 -0
  76. b1sl/b1sl/resources/_generated/approval_stages_service.py +21 -0
  77. b1sl/b1sl/resources/_generated/approval_templates.py +15 -0
  78. b1sl/b1sl/resources/_generated/approval_templates_service.py +21 -0
  79. b1sl/b1sl/resources/_generated/asset_capitalization.py +15 -0
  80. b1sl/b1sl/resources/_generated/asset_capitalization_credit_memo.py +15 -0
  81. b1sl/b1sl/resources/_generated/asset_capitalization_credit_memo_service.py +37 -0
  82. b1sl/b1sl/resources/_generated/asset_capitalization_service.py +37 -0
  83. b1sl/b1sl/resources/_generated/asset_classes.py +15 -0
  84. b1sl/b1sl/resources/_generated/asset_classes_service.py +21 -0
  85. b1sl/b1sl/resources/_generated/asset_depreciation_groups.py +15 -0
  86. b1sl/b1sl/resources/_generated/asset_depreciation_groups_service.py +21 -0
  87. b1sl/b1sl/resources/_generated/asset_groups.py +15 -0
  88. b1sl/b1sl/resources/_generated/asset_groups_service.py +21 -0
  89. b1sl/b1sl/resources/_generated/asset_manual_depreciation.py +15 -0
  90. b1sl/b1sl/resources/_generated/asset_manual_depreciation_service.py +37 -0
  91. b1sl/b1sl/resources/_generated/asset_retirement.py +15 -0
  92. b1sl/b1sl/resources/_generated/asset_retirement_service.py +37 -0
  93. b1sl/b1sl/resources/_generated/asset_transfer.py +15 -0
  94. b1sl/b1sl/resources/_generated/asset_transfer_service.py +37 -0
  95. b1sl/b1sl/resources/_generated/attachments2.py +15 -0
  96. b1sl/b1sl/resources/_generated/attribute_groups.py +15 -0
  97. b1sl/b1sl/resources/_generated/attribute_groups_service.py +21 -0
  98. b1sl/b1sl/resources/_generated/bank_charges_allocation_codes.py +15 -0
  99. b1sl/b1sl/resources/_generated/bank_charges_allocation_codes_service.py +21 -0
  100. b1sl/b1sl/resources/_generated/bank_pages.py +15 -0
  101. b1sl/b1sl/resources/_generated/bank_statements.py +15 -0
  102. b1sl/b1sl/resources/_generated/bank_statements_service.py +32 -0
  103. b1sl/b1sl/resources/_generated/banks.py +15 -0
  104. b1sl/b1sl/resources/_generated/bar_codes.py +15 -0
  105. b1sl/b1sl/resources/_generated/bar_codes_service.py +21 -0
  106. b1sl/b1sl/resources/_generated/batch_number_details.py +15 -0
  107. b1sl/b1sl/resources/_generated/bill_of_exchange_transactions.py +15 -0
  108. b1sl/b1sl/resources/_generated/bin_location_attributes.py +15 -0
  109. b1sl/b1sl/resources/_generated/bin_location_attributes_service.py +21 -0
  110. b1sl/b1sl/resources/_generated/bin_location_fields.py +15 -0
  111. b1sl/b1sl/resources/_generated/bin_location_fields_service.py +21 -0
  112. b1sl/b1sl/resources/_generated/bin_locations.py +15 -0
  113. b1sl/b1sl/resources/_generated/bin_locations_service.py +21 -0
  114. b1sl/b1sl/resources/_generated/blanket_agreements.py +15 -0
  115. b1sl/b1sl/resources/_generated/blanket_agreements_service.py +21 -0
  116. b1sl/b1sl/resources/_generated/boe_document_types.py +15 -0
  117. b1sl/b1sl/resources/_generated/boe_document_types_service.py +21 -0
  118. b1sl/b1sl/resources/_generated/boe_instructions.py +15 -0
  119. b1sl/b1sl/resources/_generated/boe_instructions_service.py +21 -0
  120. b1sl/b1sl/resources/_generated/boe_lines_service.py +30 -0
  121. b1sl/b1sl/resources/_generated/boe_portfolios.py +15 -0
  122. b1sl/b1sl/resources/_generated/boe_portfolios_service.py +21 -0
  123. b1sl/b1sl/resources/_generated/bp_fiscal_registry_id.py +15 -0
  124. b1sl/b1sl/resources/_generated/bp_opening_balance_service.py +41 -0
  125. b1sl/b1sl/resources/_generated/bp_priorities.py +15 -0
  126. b1sl/b1sl/resources/_generated/branches.py +15 -0
  127. b1sl/b1sl/resources/_generated/branches_service.py +21 -0
  128. b1sl/b1sl/resources/_generated/brazil_beverage_indexers.py +15 -0
  129. b1sl/b1sl/resources/_generated/brazil_beverage_indexers_service.py +21 -0
  130. b1sl/b1sl/resources/_generated/brazil_fuel_indexers.py +15 -0
  131. b1sl/b1sl/resources/_generated/brazil_fuel_indexers_service.py +21 -0
  132. b1sl/b1sl/resources/_generated/brazil_multi_indexers.py +15 -0
  133. b1sl/b1sl/resources/_generated/brazil_numeric_indexers.py +15 -0
  134. b1sl/b1sl/resources/_generated/brazil_string_indexers.py +15 -0
  135. b1sl/b1sl/resources/_generated/budget_distributions.py +15 -0
  136. b1sl/b1sl/resources/_generated/budget_scenarios.py +15 -0
  137. b1sl/b1sl/resources/_generated/budgets.py +15 -0
  138. b1sl/b1sl/resources/_generated/business_partner_groups.py +15 -0
  139. b1sl/b1sl/resources/_generated/business_partner_properties.py +15 -0
  140. b1sl/b1sl/resources/_generated/business_partner_properties_service.py +21 -0
  141. b1sl/b1sl/resources/_generated/business_partners.py +15 -0
  142. b1sl/b1sl/resources/_generated/business_partners_service.py +47 -0
  143. b1sl/b1sl/resources/_generated/business_places.py +15 -0
  144. b1sl/b1sl/resources/_generated/campaign_response_type.py +15 -0
  145. b1sl/b1sl/resources/_generated/campaign_response_type_service.py +21 -0
  146. b1sl/b1sl/resources/_generated/campaigns.py +15 -0
  147. b1sl/b1sl/resources/_generated/campaigns_service.py +21 -0
  148. b1sl/b1sl/resources/_generated/cash_discounts.py +15 -0
  149. b1sl/b1sl/resources/_generated/cash_discounts_service.py +21 -0
  150. b1sl/b1sl/resources/_generated/cash_flow_line_items.py +15 -0
  151. b1sl/b1sl/resources/_generated/cash_flow_line_items_service.py +21 -0
  152. b1sl/b1sl/resources/_generated/certificate_series.py +15 -0
  153. b1sl/b1sl/resources/_generated/certificate_series_service.py +21 -0
  154. b1sl/b1sl/resources/_generated/change_logs_service.py +51 -0
  155. b1sl/b1sl/resources/_generated/chart_of_accounts.py +15 -0
  156. b1sl/b1sl/resources/_generated/check_lines_service.py +38 -0
  157. b1sl/b1sl/resources/_generated/checksfor_payment.py +15 -0
  158. b1sl/b1sl/resources/_generated/choose_from_list.py +15 -0
  159. b1sl/b1sl/resources/_generated/client_mixin.py +3920 -0
  160. b1sl/b1sl/resources/_generated/closing_date_procedure.py +15 -0
  161. b1sl/b1sl/resources/_generated/cockpits.py +15 -0
  162. b1sl/b1sl/resources/_generated/cockpits_service.py +46 -0
  163. b1sl/b1sl/resources/_generated/commission_groups.py +15 -0
  164. b1sl/b1sl/resources/_generated/company_service.py +410 -0
  165. b1sl/b1sl/resources/_generated/contacts.py +15 -0
  166. b1sl/b1sl/resources/_generated/contract_templates.py +15 -0
  167. b1sl/b1sl/resources/_generated/correction_invoice.py +15 -0
  168. b1sl/b1sl/resources/_generated/correction_invoice_reversal.py +15 -0
  169. b1sl/b1sl/resources/_generated/correction_invoice_reversal_service.py +65 -0
  170. b1sl/b1sl/resources/_generated/correction_invoice_service.py +78 -0
  171. b1sl/b1sl/resources/_generated/correction_purchase_invoice.py +15 -0
  172. b1sl/b1sl/resources/_generated/correction_purchase_invoice_reversal.py +15 -0
  173. b1sl/b1sl/resources/_generated/correction_purchase_invoice_reversal_service.py +65 -0
  174. b1sl/b1sl/resources/_generated/correction_purchase_invoice_service.py +78 -0
  175. b1sl/b1sl/resources/_generated/cost_center_types.py +15 -0
  176. b1sl/b1sl/resources/_generated/cost_center_types_service.py +21 -0
  177. b1sl/b1sl/resources/_generated/cost_element_service.py +21 -0
  178. b1sl/b1sl/resources/_generated/cost_elements.py +15 -0
  179. b1sl/b1sl/resources/_generated/counties.py +15 -0
  180. b1sl/b1sl/resources/_generated/counties_service.py +21 -0
  181. b1sl/b1sl/resources/_generated/countries.py +15 -0
  182. b1sl/b1sl/resources/_generated/countries_service.py +21 -0
  183. b1sl/b1sl/resources/_generated/credit_card_payments.py +15 -0
  184. b1sl/b1sl/resources/_generated/credit_cards.py +15 -0
  185. b1sl/b1sl/resources/_generated/credit_lines_service.py +38 -0
  186. b1sl/b1sl/resources/_generated/credit_notes.py +15 -0
  187. b1sl/b1sl/resources/_generated/credit_notes_service.py +100 -0
  188. b1sl/b1sl/resources/_generated/credit_payment_methods.py +15 -0
  189. b1sl/b1sl/resources/_generated/currencies.py +15 -0
  190. b1sl/b1sl/resources/_generated/customer_equipment_cards.py +15 -0
  191. b1sl/b1sl/resources/_generated/customs_declaration.py +15 -0
  192. b1sl/b1sl/resources/_generated/customs_groups.py +15 -0
  193. b1sl/b1sl/resources/_generated/cycle_count_determinations.py +15 -0
  194. b1sl/b1sl/resources/_generated/cycle_count_determinations_service.py +21 -0
  195. b1sl/b1sl/resources/_generated/dashboard_packages_service.py +28 -0
  196. b1sl/b1sl/resources/_generated/deductible_tax_service.py +21 -0
  197. b1sl/b1sl/resources/_generated/deductible_taxes.py +15 -0
  198. b1sl/b1sl/resources/_generated/deduction_tax_groups.py +15 -0
  199. b1sl/b1sl/resources/_generated/deduction_tax_hierarchies.py +15 -0
  200. b1sl/b1sl/resources/_generated/deduction_tax_sub_groups.py +15 -0
  201. b1sl/b1sl/resources/_generated/deduction_tax_sub_groups_service.py +21 -0
  202. b1sl/b1sl/resources/_generated/delivery_notes.py +15 -0
  203. b1sl/b1sl/resources/_generated/delivery_notes_service.py +85 -0
  204. b1sl/b1sl/resources/_generated/departments.py +15 -0
  205. b1sl/b1sl/resources/_generated/departments_service.py +21 -0
  206. b1sl/b1sl/resources/_generated/deposits.py +15 -0
  207. b1sl/b1sl/resources/_generated/deposits_service.py +53 -0
  208. b1sl/b1sl/resources/_generated/depreciation_areas.py +15 -0
  209. b1sl/b1sl/resources/_generated/depreciation_areas_service.py +21 -0
  210. b1sl/b1sl/resources/_generated/depreciation_type_pools.py +15 -0
  211. b1sl/b1sl/resources/_generated/depreciation_type_pools_service.py +21 -0
  212. b1sl/b1sl/resources/_generated/depreciation_types.py +15 -0
  213. b1sl/b1sl/resources/_generated/depreciation_types_service.py +21 -0
  214. b1sl/b1sl/resources/_generated/determination_criterias.py +15 -0
  215. b1sl/b1sl/resources/_generated/determination_criterias_service.py +21 -0
  216. b1sl/b1sl/resources/_generated/dimensions.py +15 -0
  217. b1sl/b1sl/resources/_generated/dimensions_service.py +21 -0
  218. b1sl/b1sl/resources/_generated/distribution_rules.py +15 -0
  219. b1sl/b1sl/resources/_generated/distribution_rules_service.py +21 -0
  220. b1sl/b1sl/resources/_generated/dnf_code_setup.py +15 -0
  221. b1sl/b1sl/resources/_generated/dnf_code_setup_service.py +21 -0
  222. b1sl/b1sl/resources/_generated/down_payments.py +15 -0
  223. b1sl/b1sl/resources/_generated/down_payments_service.py +68 -0
  224. b1sl/b1sl/resources/_generated/drafts.py +15 -0
  225. b1sl/b1sl/resources/_generated/drafts_service.py +86 -0
  226. b1sl/b1sl/resources/_generated/dunning_letters.py +15 -0
  227. b1sl/b1sl/resources/_generated/dunning_terms.py +15 -0
  228. b1sl/b1sl/resources/_generated/dunning_terms_service.py +21 -0
  229. b1sl/b1sl/resources/_generated/dynamic_system_strings.py +15 -0
  230. b1sl/b1sl/resources/_generated/electronic_communication_action_service.py +122 -0
  231. b1sl/b1sl/resources/_generated/electronic_communication_actions_service.py +119 -0
  232. b1sl/b1sl/resources/_generated/electronic_file_formats.py +15 -0
  233. b1sl/b1sl/resources/_generated/electronic_file_formats_service.py +21 -0
  234. b1sl/b1sl/resources/_generated/email_groups.py +15 -0
  235. b1sl/b1sl/resources/_generated/email_groups_service.py +21 -0
  236. b1sl/b1sl/resources/_generated/employee_id_type.py +15 -0
  237. b1sl/b1sl/resources/_generated/employee_id_type_service.py +21 -0
  238. b1sl/b1sl/resources/_generated/employee_position.py +15 -0
  239. b1sl/b1sl/resources/_generated/employee_position_service.py +21 -0
  240. b1sl/b1sl/resources/_generated/employee_roles_setup.py +15 -0
  241. b1sl/b1sl/resources/_generated/employee_roles_setup_service.py +21 -0
  242. b1sl/b1sl/resources/_generated/employee_status.py +15 -0
  243. b1sl/b1sl/resources/_generated/employee_status_service.py +21 -0
  244. b1sl/b1sl/resources/_generated/employee_transfers.py +15 -0
  245. b1sl/b1sl/resources/_generated/employee_transfers_service.py +21 -0
  246. b1sl/b1sl/resources/_generated/employees_info.py +15 -0
  247. b1sl/b1sl/resources/_generated/employment_category_service.py +21 -0
  248. b1sl/b1sl/resources/_generated/employment_categorys.py +15 -0
  249. b1sl/b1sl/resources/_generated/enhanced_discount_groups.py +15 -0
  250. b1sl/b1sl/resources/_generated/enhanced_discount_groups_service.py +21 -0
  251. b1sl/b1sl/resources/_generated/exceptional_event_service.py +21 -0
  252. b1sl/b1sl/resources/_generated/exceptional_events.py +15 -0
  253. b1sl/b1sl/resources/_generated/extended_translations.py +15 -0
  254. b1sl/b1sl/resources/_generated/extended_translations_service.py +21 -0
  255. b1sl/b1sl/resources/_generated/external_calls_service.py +54 -0
  256. b1sl/b1sl/resources/_generated/external_reconciliations_service.py +104 -0
  257. b1sl/b1sl/resources/_generated/fa_account_determinations.py +15 -0
  258. b1sl/b1sl/resources/_generated/fa_account_determinations_service.py +21 -0
  259. b1sl/b1sl/resources/_generated/factoring_indicators.py +15 -0
  260. b1sl/b1sl/resources/_generated/financial_years.py +15 -0
  261. b1sl/b1sl/resources/_generated/financial_years_service.py +21 -0
  262. b1sl/b1sl/resources/_generated/fiscal_printer.py +15 -0
  263. b1sl/b1sl/resources/_generated/fiscal_printer_service.py +21 -0
  264. b1sl/b1sl/resources/_generated/fixed_asset_items_service.py +55 -0
  265. b1sl/b1sl/resources/_generated/form_preferences.py +15 -0
  266. b1sl/b1sl/resources/_generated/formatted_searches.py +15 -0
  267. b1sl/b1sl/resources/_generated/forms1099.py +15 -0
  268. b1sl/b1sl/resources/_generated/gl_account_advanced_rules.py +15 -0
  269. b1sl/b1sl/resources/_generated/gl_account_advanced_rules_service.py +21 -0
  270. b1sl/b1sl/resources/_generated/goods_return_request.py +15 -0
  271. b1sl/b1sl/resources/_generated/goods_return_request_service.py +69 -0
  272. b1sl/b1sl/resources/_generated/gov_pay_codes.py +15 -0
  273. b1sl/b1sl/resources/_generated/gov_pay_codes_service.py +21 -0
  274. b1sl/b1sl/resources/_generated/gt_is_service.py +28 -0
  275. b1sl/b1sl/resources/_generated/holidays.py +15 -0
  276. b1sl/b1sl/resources/_generated/house_bank_accounts.py +15 -0
  277. b1sl/b1sl/resources/_generated/incoming_payments.py +15 -0
  278. b1sl/b1sl/resources/_generated/india_hsn.py +15 -0
  279. b1sl/b1sl/resources/_generated/india_hsn_service.py +21 -0
  280. b1sl/b1sl/resources/_generated/industries.py +15 -0
  281. b1sl/b1sl/resources/_generated/integration_packages_configure.py +15 -0
  282. b1sl/b1sl/resources/_generated/integration_packages_configure_service.py +21 -0
  283. b1sl/b1sl/resources/_generated/internal_reconciliations.py +15 -0
  284. b1sl/b1sl/resources/_generated/internal_reconciliations_service.py +51 -0
  285. b1sl/b1sl/resources/_generated/intrastat_configuration.py +15 -0
  286. b1sl/b1sl/resources/_generated/intrastat_configuration_service.py +21 -0
  287. b1sl/b1sl/resources/_generated/inventory_countings.py +15 -0
  288. b1sl/b1sl/resources/_generated/inventory_countings_service.py +21 -0
  289. b1sl/b1sl/resources/_generated/inventory_cycles.py +15 -0
  290. b1sl/b1sl/resources/_generated/inventory_gen_entries.py +15 -0
  291. b1sl/b1sl/resources/_generated/inventory_gen_entry_service.py +67 -0
  292. b1sl/b1sl/resources/_generated/inventory_gen_exit_service.py +66 -0
  293. b1sl/b1sl/resources/_generated/inventory_gen_exits.py +15 -0
  294. b1sl/b1sl/resources/_generated/inventory_opening_balances.py +15 -0
  295. b1sl/b1sl/resources/_generated/inventory_opening_balances_service.py +21 -0
  296. b1sl/b1sl/resources/_generated/inventory_postings.py +15 -0
  297. b1sl/b1sl/resources/_generated/inventory_postings_service.py +34 -0
  298. b1sl/b1sl/resources/_generated/inventory_transfer_requests.py +15 -0
  299. b1sl/b1sl/resources/_generated/inventory_transfer_requests_service.py +43 -0
  300. b1sl/b1sl/resources/_generated/invoices.py +15 -0
  301. b1sl/b1sl/resources/_generated/invoices_service.py +100 -0
  302. b1sl/b1sl/resources/_generated/item_groups.py +15 -0
  303. b1sl/b1sl/resources/_generated/item_properties.py +15 -0
  304. b1sl/b1sl/resources/_generated/items.py +15 -0
  305. b1sl/b1sl/resources/_generated/journal_entries.py +15 -0
  306. b1sl/b1sl/resources/_generated/journal_entry_document_type_service.py +21 -0
  307. b1sl/b1sl/resources/_generated/journal_entry_document_types.py +15 -0
  308. b1sl/b1sl/resources/_generated/journal_vouchers_service.py +45 -0
  309. b1sl/b1sl/resources/_generated/knowledge_base_solutions.py +15 -0
  310. b1sl/b1sl/resources/_generated/kp_is.py +15 -0
  311. b1sl/b1sl/resources/_generated/kp_is_service.py +21 -0
  312. b1sl/b1sl/resources/_generated/landed_costs.py +15 -0
  313. b1sl/b1sl/resources/_generated/landed_costs_codes.py +15 -0
  314. b1sl/b1sl/resources/_generated/landed_costs_service.py +21 -0
  315. b1sl/b1sl/resources/_generated/legal_data.py +15 -0
  316. b1sl/b1sl/resources/_generated/length_measures.py +15 -0
  317. b1sl/b1sl/resources/_generated/license_service.py +21 -0
  318. b1sl/b1sl/resources/_generated/local_era.py +15 -0
  319. b1sl/b1sl/resources/_generated/manufacturers.py +15 -0
  320. b1sl/b1sl/resources/_generated/material_groups.py +15 -0
  321. b1sl/b1sl/resources/_generated/material_groups_service.py +21 -0
  322. b1sl/b1sl/resources/_generated/material_revaluation.py +15 -0
  323. b1sl/b1sl/resources/_generated/material_revaluation_fifo_service.py +33 -0
  324. b1sl/b1sl/resources/_generated/material_revaluation_snb_service.py +33 -0
  325. b1sl/b1sl/resources/_generated/messages.py +15 -0
  326. b1sl/b1sl/resources/_generated/messages_service.py +35 -0
  327. b1sl/b1sl/resources/_generated/mobile_add_on_setting.py +15 -0
  328. b1sl/b1sl/resources/_generated/mobile_add_on_setting_service.py +21 -0
  329. b1sl/b1sl/resources/_generated/mobile_app_service.py +240 -0
  330. b1sl/b1sl/resources/_generated/multi_language_translations.py +15 -0
  331. b1sl/b1sl/resources/_generated/nature_of_assessees.py +15 -0
  332. b1sl/b1sl/resources/_generated/nature_of_assessees_service.py +21 -0
  333. b1sl/b1sl/resources/_generated/ncm_codes_setup.py +15 -0
  334. b1sl/b1sl/resources/_generated/ncm_codes_setup_service.py +21 -0
  335. b1sl/b1sl/resources/_generated/nf_models.py +15 -0
  336. b1sl/b1sl/resources/_generated/nf_models_service.py +21 -0
  337. b1sl/b1sl/resources/_generated/nf_tax_categories.py +15 -0
  338. b1sl/b1sl/resources/_generated/nf_tax_categories_service.py +21 -0
  339. b1sl/b1sl/resources/_generated/nota_fiscal_cfop.py +15 -0
  340. b1sl/b1sl/resources/_generated/nota_fiscal_cst.py +15 -0
  341. b1sl/b1sl/resources/_generated/nota_fiscal_usage.py +15 -0
  342. b1sl/b1sl/resources/_generated/occurrence_codes.py +15 -0
  343. b1sl/b1sl/resources/_generated/occurrence_codes_service.py +21 -0
  344. b1sl/b1sl/resources/_generated/orders.py +15 -0
  345. b1sl/b1sl/resources/_generated/orders_service.py +94 -0
  346. b1sl/b1sl/resources/_generated/packages_types.py +15 -0
  347. b1sl/b1sl/resources/_generated/partners_setups.py +15 -0
  348. b1sl/b1sl/resources/_generated/partners_setups_service.py +21 -0
  349. b1sl/b1sl/resources/_generated/payment_blocks.py +15 -0
  350. b1sl/b1sl/resources/_generated/payment_blocks_service.py +21 -0
  351. b1sl/b1sl/resources/_generated/payment_calculation_service.py +37 -0
  352. b1sl/b1sl/resources/_generated/payment_drafts.py +15 -0
  353. b1sl/b1sl/resources/_generated/payment_reason_code_service.py +21 -0
  354. b1sl/b1sl/resources/_generated/payment_reason_codes.py +15 -0
  355. b1sl/b1sl/resources/_generated/payment_run_export.py +15 -0
  356. b1sl/b1sl/resources/_generated/payment_terms_types.py +15 -0
  357. b1sl/b1sl/resources/_generated/payment_terms_types_service.py +28 -0
  358. b1sl/b1sl/resources/_generated/pick_lists.py +15 -0
  359. b1sl/b1sl/resources/_generated/pick_lists_service.py +78 -0
  360. b1sl/b1sl/resources/_generated/pos_daily_summary.py +15 -0
  361. b1sl/b1sl/resources/_generated/predefined_texts.py +15 -0
  362. b1sl/b1sl/resources/_generated/predefined_texts_service.py +21 -0
  363. b1sl/b1sl/resources/_generated/price_lists.py +15 -0
  364. b1sl/b1sl/resources/_generated/product_trees.py +15 -0
  365. b1sl/b1sl/resources/_generated/production_orders.py +15 -0
  366. b1sl/b1sl/resources/_generated/profit_centers.py +15 -0
  367. b1sl/b1sl/resources/_generated/profit_centers_service.py +21 -0
  368. b1sl/b1sl/resources/_generated/project_management_configuration_service.py +333 -0
  369. b1sl/b1sl/resources/_generated/project_management_service.py +104 -0
  370. b1sl/b1sl/resources/_generated/project_management_time_sheet.py +15 -0
  371. b1sl/b1sl/resources/_generated/project_managements.py +15 -0
  372. b1sl/b1sl/resources/_generated/projects.py +15 -0
  373. b1sl/b1sl/resources/_generated/projects_service.py +21 -0
  374. b1sl/b1sl/resources/_generated/purchase_credit_notes.py +15 -0
  375. b1sl/b1sl/resources/_generated/purchase_credit_notes_service.py +85 -0
  376. b1sl/b1sl/resources/_generated/purchase_delivery_notes.py +15 -0
  377. b1sl/b1sl/resources/_generated/purchase_delivery_notes_service.py +85 -0
  378. b1sl/b1sl/resources/_generated/purchase_down_payments.py +15 -0
  379. b1sl/b1sl/resources/_generated/purchase_down_payments_service.py +70 -0
  380. b1sl/b1sl/resources/_generated/purchase_invoices.py +15 -0
  381. b1sl/b1sl/resources/_generated/purchase_invoices_service.py +85 -0
  382. b1sl/b1sl/resources/_generated/purchase_orders.py +15 -0
  383. b1sl/b1sl/resources/_generated/purchase_orders_service.py +69 -0
  384. b1sl/b1sl/resources/_generated/purchase_quotations.py +15 -0
  385. b1sl/b1sl/resources/_generated/purchase_quotations_service.py +70 -0
  386. b1sl/b1sl/resources/_generated/purchase_request_service.py +68 -0
  387. b1sl/b1sl/resources/_generated/purchase_requests.py +15 -0
  388. b1sl/b1sl/resources/_generated/purchase_returns.py +15 -0
  389. b1sl/b1sl/resources/_generated/purchase_returns_service.py +85 -0
  390. b1sl/b1sl/resources/_generated/purchase_tax_invoices.py +15 -0
  391. b1sl/b1sl/resources/_generated/qr_code_service.py +33 -0
  392. b1sl/b1sl/resources/_generated/query_auth_groups.py +15 -0
  393. b1sl/b1sl/resources/_generated/query_categories.py +15 -0
  394. b1sl/b1sl/resources/_generated/query_service.py +29 -0
  395. b1sl/b1sl/resources/_generated/queue.py +15 -0
  396. b1sl/b1sl/resources/_generated/quotations.py +15 -0
  397. b1sl/b1sl/resources/_generated/quotations_service.py +69 -0
  398. b1sl/b1sl/resources/_generated/recurring_transaction_service.py +67 -0
  399. b1sl/b1sl/resources/_generated/relationships.py +15 -0
  400. b1sl/b1sl/resources/_generated/report_filter.py +15 -0
  401. b1sl/b1sl/resources/_generated/report_filter_service.py +30 -0
  402. b1sl/b1sl/resources/_generated/report_layouts_service.py +237 -0
  403. b1sl/b1sl/resources/_generated/report_types.py +15 -0
  404. b1sl/b1sl/resources/_generated/report_types_service.py +21 -0
  405. b1sl/b1sl/resources/_generated/resource_capacities.py +15 -0
  406. b1sl/b1sl/resources/_generated/resource_capacities_service.py +34 -0
  407. b1sl/b1sl/resources/_generated/resource_groups.py +15 -0
  408. b1sl/b1sl/resources/_generated/resource_groups_service.py +21 -0
  409. b1sl/b1sl/resources/_generated/resource_properties.py +15 -0
  410. b1sl/b1sl/resources/_generated/resource_properties_service.py +21 -0
  411. b1sl/b1sl/resources/_generated/resources.py +15 -0
  412. b1sl/b1sl/resources/_generated/resources_service.py +21 -0
  413. b1sl/b1sl/resources/_generated/retorno_codes.py +15 -0
  414. b1sl/b1sl/resources/_generated/retorno_codes_service.py +21 -0
  415. b1sl/b1sl/resources/_generated/return_request.py +15 -0
  416. b1sl/b1sl/resources/_generated/return_request_service.py +69 -0
  417. b1sl/b1sl/resources/_generated/returns.py +15 -0
  418. b1sl/b1sl/resources/_generated/returns_service.py +85 -0
  419. b1sl/b1sl/resources/_generated/route_stages.py +15 -0
  420. b1sl/b1sl/resources/_generated/route_stages_service.py +21 -0
  421. b1sl/b1sl/resources/_generated/sales_forecast.py +15 -0
  422. b1sl/b1sl/resources/_generated/sales_opportunities.py +15 -0
  423. b1sl/b1sl/resources/_generated/sales_opportunity_competitors_setup.py +15 -0
  424. b1sl/b1sl/resources/_generated/sales_opportunity_competitors_setup_service.py +21 -0
  425. b1sl/b1sl/resources/_generated/sales_opportunity_interests_setup.py +15 -0
  426. b1sl/b1sl/resources/_generated/sales_opportunity_interests_setup_service.py +21 -0
  427. b1sl/b1sl/resources/_generated/sales_opportunity_reasons_setup.py +15 -0
  428. b1sl/b1sl/resources/_generated/sales_opportunity_reasons_setup_service.py +21 -0
  429. b1sl/b1sl/resources/_generated/sales_opportunity_sources_setup.py +15 -0
  430. b1sl/b1sl/resources/_generated/sales_opportunity_sources_setup_service.py +21 -0
  431. b1sl/b1sl/resources/_generated/sales_persons.py +15 -0
  432. b1sl/b1sl/resources/_generated/sales_stages.py +15 -0
  433. b1sl/b1sl/resources/_generated/sales_tax_authorities.py +15 -0
  434. b1sl/b1sl/resources/_generated/sales_tax_authorities_types.py +15 -0
  435. b1sl/b1sl/resources/_generated/sales_tax_codes.py +15 -0
  436. b1sl/b1sl/resources/_generated/sales_tax_invoices.py +15 -0
  437. b1sl/b1sl/resources/_generated/sbo_bob_service.py +89 -0
  438. b1sl/b1sl/resources/_generated/sections.py +15 -0
  439. b1sl/b1sl/resources/_generated/sections_service.py +21 -0
  440. b1sl/b1sl/resources/_generated/self_credit_memo_service.py +69 -0
  441. b1sl/b1sl/resources/_generated/self_credit_memos.py +15 -0
  442. b1sl/b1sl/resources/_generated/self_invoice_service.py +69 -0
  443. b1sl/b1sl/resources/_generated/self_invoices.py +15 -0
  444. b1sl/b1sl/resources/_generated/serial_number_details.py +15 -0
  445. b1sl/b1sl/resources/_generated/series_service.py +305 -0
  446. b1sl/b1sl/resources/_generated/service_call_origins.py +15 -0
  447. b1sl/b1sl/resources/_generated/service_call_origins_service.py +21 -0
  448. b1sl/b1sl/resources/_generated/service_call_problem_sub_types.py +15 -0
  449. b1sl/b1sl/resources/_generated/service_call_problem_sub_types_service.py +21 -0
  450. b1sl/b1sl/resources/_generated/service_call_problem_types.py +15 -0
  451. b1sl/b1sl/resources/_generated/service_call_problem_types_service.py +21 -0
  452. b1sl/b1sl/resources/_generated/service_call_solution_status.py +15 -0
  453. b1sl/b1sl/resources/_generated/service_call_solution_status_service.py +21 -0
  454. b1sl/b1sl/resources/_generated/service_call_status.py +15 -0
  455. b1sl/b1sl/resources/_generated/service_call_status_service.py +21 -0
  456. b1sl/b1sl/resources/_generated/service_call_types.py +15 -0
  457. b1sl/b1sl/resources/_generated/service_call_types_service.py +21 -0
  458. b1sl/b1sl/resources/_generated/service_calls.py +15 -0
  459. b1sl/b1sl/resources/_generated/service_contracts.py +15 -0
  460. b1sl/b1sl/resources/_generated/service_groups.py +15 -0
  461. b1sl/b1sl/resources/_generated/service_groups_service.py +21 -0
  462. b1sl/b1sl/resources/_generated/service_tax_posting_service.py +36 -0
  463. b1sl/b1sl/resources/_generated/shipping_types.py +15 -0
  464. b1sl/b1sl/resources/_generated/short_link_mappings_service.py +26 -0
  465. b1sl/b1sl/resources/_generated/special_prices.py +15 -0
  466. b1sl/b1sl/resources/_generated/states.py +15 -0
  467. b1sl/b1sl/resources/_generated/states_service.py +21 -0
  468. b1sl/b1sl/resources/_generated/stock_takings.py +15 -0
  469. b1sl/b1sl/resources/_generated/stock_transfer_draft_service.py +34 -0
  470. b1sl/b1sl/resources/_generated/stock_transfer_drafts.py +15 -0
  471. b1sl/b1sl/resources/_generated/stock_transfer_service.py +43 -0
  472. b1sl/b1sl/resources/_generated/stock_transfers.py +15 -0
  473. b1sl/b1sl/resources/_generated/target_groups.py +15 -0
  474. b1sl/b1sl/resources/_generated/target_groups_service.py +21 -0
  475. b1sl/b1sl/resources/_generated/tax_code_determinations.py +15 -0
  476. b1sl/b1sl/resources/_generated/tax_code_determinations_service.py +21 -0
  477. b1sl/b1sl/resources/_generated/tax_code_determinations_tcd.py +15 -0
  478. b1sl/b1sl/resources/_generated/tax_code_determinations_tcd_service.py +21 -0
  479. b1sl/b1sl/resources/_generated/tax_invoice_report.py +15 -0
  480. b1sl/b1sl/resources/_generated/tax_web_sites.py +15 -0
  481. b1sl/b1sl/resources/_generated/tax_web_sites_service.py +27 -0
  482. b1sl/b1sl/resources/_generated/teams.py +15 -0
  483. b1sl/b1sl/resources/_generated/termination_reason.py +15 -0
  484. b1sl/b1sl/resources/_generated/termination_reason_service.py +21 -0
  485. b1sl/b1sl/resources/_generated/territories.py +15 -0
  486. b1sl/b1sl/resources/_generated/tracking_notes.py +15 -0
  487. b1sl/b1sl/resources/_generated/tracking_notes_service.py +21 -0
  488. b1sl/b1sl/resources/_generated/transaction_codes.py +15 -0
  489. b1sl/b1sl/resources/_generated/transaction_codes_service.py +21 -0
  490. b1sl/b1sl/resources/_generated/transportation_document.py +15 -0
  491. b1sl/b1sl/resources/_generated/tsr_exceptional_event_service.py +21 -0
  492. b1sl/b1sl/resources/_generated/tsr_exceptional_events.py +15 -0
  493. b1sl/b1sl/resources/_generated/unit_of_measurement_groups.py +15 -0
  494. b1sl/b1sl/resources/_generated/unit_of_measurement_groups_service.py +21 -0
  495. b1sl/b1sl/resources/_generated/unit_of_measurements.py +15 -0
  496. b1sl/b1sl/resources/_generated/unit_of_measurements_service.py +21 -0
  497. b1sl/b1sl/resources/_generated/user_default_groups.py +15 -0
  498. b1sl/b1sl/resources/_generated/user_fields_md.py +15 -0
  499. b1sl/b1sl/resources/_generated/user_keys_md.py +15 -0
  500. b1sl/b1sl/resources/_generated/user_languages.py +15 -0
  501. b1sl/b1sl/resources/_generated/user_menu_service.py +103 -0
  502. b1sl/b1sl/resources/_generated/user_objects_md.py +15 -0
  503. b1sl/b1sl/resources/_generated/user_permission_tree.py +15 -0
  504. b1sl/b1sl/resources/_generated/user_queries.py +15 -0
  505. b1sl/b1sl/resources/_generated/user_tables_md.py +15 -0
  506. b1sl/b1sl/resources/_generated/users.py +15 -0
  507. b1sl/b1sl/resources/_generated/users_service.py +21 -0
  508. b1sl/b1sl/resources/_generated/value_mapping.py +15 -0
  509. b1sl/b1sl/resources/_generated/value_mapping_communication.py +15 -0
  510. b1sl/b1sl/resources/_generated/value_mapping_service.py +64 -0
  511. b1sl/b1sl/resources/_generated/vat_groups.py +15 -0
  512. b1sl/b1sl/resources/_generated/vendor_payments.py +15 -0
  513. b1sl/b1sl/resources/_generated/w_tax_type_code_service.py +21 -0
  514. b1sl/b1sl/resources/_generated/w_tax_type_codes.py +15 -0
  515. b1sl/b1sl/resources/_generated/warehouse_locations.py +15 -0
  516. b1sl/b1sl/resources/_generated/warehouse_sublevel_codes.py +15 -0
  517. b1sl/b1sl/resources/_generated/warehouse_sublevel_codes_service.py +21 -0
  518. b1sl/b1sl/resources/_generated/warehouses.py +15 -0
  519. b1sl/b1sl/resources/_generated/web_client_bookmark_tile_service.py +21 -0
  520. b1sl/b1sl/resources/_generated/web_client_bookmark_tiles.py +15 -0
  521. b1sl/b1sl/resources/_generated/web_client_dashboard_service.py +21 -0
  522. b1sl/b1sl/resources/_generated/web_client_dashboards.py +15 -0
  523. b1sl/b1sl/resources/_generated/web_client_form_setting_service.py +21 -0
  524. b1sl/b1sl/resources/_generated/web_client_form_settings.py +15 -0
  525. b1sl/b1sl/resources/_generated/web_client_launchpad_service.py +21 -0
  526. b1sl/b1sl/resources/_generated/web_client_launchpads.py +15 -0
  527. b1sl/b1sl/resources/_generated/web_client_listview_filter_service.py +21 -0
  528. b1sl/b1sl/resources/_generated/web_client_listview_filters.py +15 -0
  529. b1sl/b1sl/resources/_generated/web_client_notification_service.py +21 -0
  530. b1sl/b1sl/resources/_generated/web_client_notifications.py +15 -0
  531. b1sl/b1sl/resources/_generated/web_client_preference_service.py +21 -0
  532. b1sl/b1sl/resources/_generated/web_client_preferences.py +15 -0
  533. b1sl/b1sl/resources/_generated/web_client_recent_activities.py +15 -0
  534. b1sl/b1sl/resources/_generated/web_client_recent_activity_service.py +21 -0
  535. b1sl/b1sl/resources/_generated/web_client_variant_group_service.py +21 -0
  536. b1sl/b1sl/resources/_generated/web_client_variant_groups.py +15 -0
  537. b1sl/b1sl/resources/_generated/web_client_variant_service.py +21 -0
  538. b1sl/b1sl/resources/_generated/web_client_variants.py +15 -0
  539. b1sl/b1sl/resources/_generated/weight_measures.py +15 -0
  540. b1sl/b1sl/resources/_generated/withholding_tax_codes.py +15 -0
  541. b1sl/b1sl/resources/_generated/witholding_tax_definition.py +15 -0
  542. b1sl/b1sl/resources/_generated/wizard_payment_methods.py +15 -0
  543. b1sl/b1sl/resources/_generated/workflow_task_service.py +47 -0
  544. b1sl/b1sl/resources/async_base.py +60 -0
  545. b1sl/b1sl/resources/base.py +159 -0
  546. b1sl/b1sl/resources/udo.py +78 -0
  547. b1sl/b1sl/rest_adapter.py +351 -0
  548. b1sl/b1sl/testing.py +24 -0
  549. b1sl/b1sl/tests/__init__.py +0 -0
  550. b1sl/b1sl/tests/test_01_settings.py +60 -0
  551. b1sl/b1sl/tests/test_02_rest_adapter.py +177 -0
  552. b1sl/b1sl/tests/test_03_items_endpoint.py +172 -0
  553. b1sl/b1sl/tests/test_04_serialnumberdetails_endpoint.py +181 -0
  554. b1sl/b1sl/tests/test_observability.py +153 -0
  555. b1sl/b1sl/tests/test_utils.py +24 -0
  556. b1sl/contrib/__init__.py +0 -0
  557. b1sl/contrib/django/__init__.py +15 -0
  558. b1sl/contrib/django/base.py +89 -0
  559. b1sl/contrib/django/odata_decode_middleware.py +14 -0
  560. b1sl/contrib/django/odata_transform_url_middleware.py +15 -0
  561. b1sl/py.typed +0 -0
  562. b1sl/saphdb/__init__.py +19 -0
  563. b1sl/saphdb/adapter.py +24 -0
  564. b1sl/saphdb/client.py +50 -0
  565. b1sl/saphdb/config.py +68 -0
  566. b1sl/saphdb/endpoints/serialnumberdetailodbc.py +100 -0
  567. b1sl/saphdb/exceptions/exceptions.py +19 -0
  568. b1sl/saphdb/main.py +18 -0
  569. b1sl/saphdb/models/result.py +18 -0
  570. b1sl/saphdb/models/serial.py +37 -0
  571. b1sl/saphdb/odbc_adapter.py +220 -0
  572. b1sl/saphdb/tests/__init__.py +0 -0
  573. b1sl/saphdb/tests/test_01_settings.py +37 -0
  574. b1sl/saphdb/tests/test_02_odbc_adapter.py +96 -0
  575. b1sl/saphdb/tests/test_04_serialnumberdetails_endpoint.py +131 -0
  576. b1sl_python-0.1.0.dist-info/METADATA +169 -0
  577. b1sl_python-0.1.0.dist-info/RECORD +579 -0
  578. b1sl_python-0.1.0.dist-info/WHEEL +4 -0
  579. b1sl_python-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,351 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import logging
5
+ import threading
6
+ import time
7
+ from datetime import datetime, timedelta
8
+ from json import JSONDecodeError
9
+ from typing import Optional
10
+ from urllib.parse import urlencode, urlsplit
11
+
12
+ import httpx
13
+
14
+ from b1sl.b1sl.base_adapter import BaseRestAdapter, HookContext, ObservabilityConfig
15
+ from b1sl.b1sl.config import B1Config
16
+ from b1sl.b1sl.exceptions.exceptions import (
17
+ B1AuthError,
18
+ B1Exception,
19
+ B1NotFoundError,
20
+ B1ValidationError,
21
+ SAPConcurrencyError,
22
+ )
23
+ from b1sl.b1sl.models.result import Result
24
+
25
+ _HTTP_STATUS_TO_EXC: dict[int, type] = {
26
+ 400: B1ValidationError,
27
+ 401: B1AuthError,
28
+ 404: B1NotFoundError,
29
+ }
30
+
31
+
32
+ class RestAdapter(BaseRestAdapter):
33
+ """
34
+ Synchronous HTTP adapter for SAP B1 Service Layer using requests.
35
+
36
+ This adapter manages session lifecycle (login/logout), re-authentication
37
+ on 401 errors, and ETag-based optimistic concurrency for single-threaded
38
+ or WSGI environments. It uses httpx.Client for parity with the async client.
39
+
40
+ AI Role: Use for synchronous workflows or legacy integrations.
41
+ Handles basic CRUD operations and session resilience automatically.
42
+ """
43
+
44
+
45
+
46
+ def __init__(self, *args, **kwargs):
47
+ """
48
+ Initializes the synchronous httpx client.
49
+ """
50
+ super().__init__(*args, **kwargs)
51
+ self._lock = threading.Lock()
52
+ self.session = httpx.Client(
53
+ verify=self._ssl_verify,
54
+ timeout=httpx.Timeout(
55
+ self._connect_timeout,
56
+ read=self._read_timeout,
57
+ write=self._read_timeout,
58
+ pool=self._connect_timeout,
59
+ ),
60
+ follow_redirects=True,
61
+ )
62
+
63
+ def close(self) -> None:
64
+ """Closes the underlying HTTP client."""
65
+ if self.session:
66
+ self.session.close()
67
+
68
+ @property
69
+ def session_id(self) -> str | None:
70
+ """
71
+ Retrieves the current SAP session ID from the cookie jar.
72
+
73
+ Returns:
74
+ str: The B1SESSION cookie value or None.
75
+ """
76
+ return self.session.cookies.get("B1SESSION")
77
+
78
+ @staticmethod
79
+ def handle_token(manage_token: bool = True):
80
+ """
81
+ Decorator for methods requiring a valid SAP session.
82
+
83
+ Logs in before the method call if needed and potentially logs out
84
+ afterwards depending on the reuse_token configuration.
85
+
86
+ Args:
87
+ manage_token (bool): Whether to perform session lifecycle management.
88
+ """
89
+
90
+ def decorator(func):
91
+ @functools.wraps(func)
92
+ def wrapper(self, *args, **kwargs):
93
+ if manage_token:
94
+ self._handle_token_login()
95
+ try:
96
+ result = func(self, *args, **kwargs)
97
+ finally:
98
+ if manage_token:
99
+ self._handle_token_logout()
100
+ return result
101
+
102
+ return wrapper
103
+
104
+ return decorator
105
+
106
+ def login(self):
107
+ """Authenticate with the SAP B1 Service Layer."""
108
+ return self._login()
109
+
110
+ def logout(self):
111
+ """Release the SAP B1 session license."""
112
+ return self._logout()
113
+
114
+ def _login(self):
115
+ """
116
+ Internal login implementation.
117
+
118
+ Calls POST /Login and updates session state and expiry.
119
+
120
+ Returns:
121
+ Result: Response from the Login endpoint.
122
+ """
123
+ data = {
124
+ "UserName": self._username,
125
+ "Password": self._password,
126
+ "CompanyDB": self._db,
127
+ }
128
+ self._logger.info(f"Logging in to SAP B1 at {self.raw_base_url}...")
129
+
130
+ response = self._do(
131
+ http_method="POST", endpoint="/Login", data=data, _is_login=True
132
+ )
133
+ if response.status_code == 200:
134
+ self.is_session_active = True
135
+ timeout_min = response.data.get("SessionTimeout", 30)
136
+ self.token_expiry = datetime.now() + timedelta(minutes=timeout_min - 2)
137
+ return response
138
+
139
+ def _logout(self):
140
+ """
141
+ Internal logout implementation.
142
+
143
+ Returns:
144
+ Result: Response from the Logout endpoint.
145
+ """
146
+ response = self._do(http_method="POST", endpoint="/Logout", _is_login=True)
147
+ if response.status_code == 204:
148
+ self.is_session_active = False
149
+ self.token_expiry = None
150
+ return response
151
+
152
+ def _is_token_expire(self) -> bool:
153
+ """
154
+ Checks if the current session token is missing or expired.
155
+
156
+ Returns:
157
+ bool: True if a new login is required.
158
+ """
159
+ return self.token_expiry is None or datetime.now() >= self.token_expiry
160
+
161
+ def _handle_token_login(self):
162
+ """Standard login handler used by the decorator."""
163
+ if self.reuse_token:
164
+ if self._is_token_expire():
165
+ with self._lock:
166
+ if self._is_token_expire():
167
+ self._login()
168
+ else:
169
+ with self._lock:
170
+ self._login()
171
+
172
+ def _handle_token_logout(self):
173
+ """Standard logout handler used by the decorator."""
174
+ if not self.reuse_token:
175
+ self._logout()
176
+
177
+ def _get_full_url(self, next_link: str) -> str:
178
+ """Normalizes a nextLink to a relative resource path."""
179
+ return "/" + urlsplit(next_link).path.lstrip("/")
180
+
181
+ def _process_next_link(self, next_link: str) -> tuple[str, str, dict]:
182
+ """Parses an OData nextLink for multi-page results."""
183
+ return (
184
+ self._clean_url(next_link),
185
+ self._get_full_url(next_link),
186
+ self._get_ep_params(next_link),
187
+ )
188
+
189
+ @staticmethod
190
+ def _parse_sap_error(response: httpx.Response) -> tuple[str, str]:
191
+ """Parses error information from an Httpx response object."""
192
+ try:
193
+ body = response.json()
194
+ except Exception:
195
+ body = None
196
+ return RestAdapter._parse_sap_error_shared(response.status_code, response.reason_phrase, body)
197
+
198
+ def _execute_request(
199
+ self,
200
+ http_method: str,
201
+ full_url: str,
202
+ headers: dict,
203
+ ep_params: dict | None,
204
+ data: dict | None,
205
+ ) -> httpx.Response:
206
+ """Executes the raw HTTP request using the httpx client."""
207
+ return self.session.request(
208
+ method=http_method,
209
+ url=full_url,
210
+ headers=headers,
211
+ params=ep_params,
212
+ json=data,
213
+ )
214
+
215
+ def _do(
216
+ self,
217
+ http_method: str,
218
+ endpoint: str,
219
+ ep_params: dict | None = None,
220
+ data: dict | None = None,
221
+ _is_login: bool = False,
222
+ ) -> Result:
223
+ """
224
+ Dispatches a standardized HTTP request to SAP.
225
+ Implements Senior Observability (Timing + Structured Logging + Hooks).
226
+ """
227
+ # Removed redundant imports moved to module level
228
+ req_id = self._generate_req_id()
229
+ endpoint_path = endpoint if endpoint.startswith("/") else f"/{endpoint}"
230
+ full_url = self.raw_base_url + endpoint_path
231
+
232
+ # ── ETag: inject If-None-Match (GET) or If-Match (PATCH/DELETE/POST) ──
233
+ headers = self._build_headers(http_method, endpoint_path)
234
+
235
+ log_data = self._redact_data(data)
236
+ self._logger.debug(f"[{req_id}] data={log_data}")
237
+
238
+ start_time = time.perf_counter()
239
+ exc_captured: Exception | None = None
240
+ response: httpx.Response | None = None
241
+ is_success = False
242
+
243
+ try:
244
+ response = self._execute_request(
245
+ http_method, full_url, headers, ep_params, data
246
+ )
247
+ response.raise_for_status()
248
+ is_success = True
249
+ except httpx.HTTPStatusError as e:
250
+ if response is not None and response.status_code == 401 and not _is_login:
251
+ self._logger.warning(f"[{req_id}] 401 Unauthorized - retrying login...")
252
+ try:
253
+ old_token_expiry = self.token_expiry
254
+ with self._lock:
255
+ if self.token_expiry == old_token_expiry:
256
+ self._login()
257
+ response = self._execute_request(
258
+ http_method, full_url, headers, ep_params, data
259
+ )
260
+ response.raise_for_status()
261
+ is_success = True
262
+ except Exception as retry_exc:
263
+ exc_captured = retry_exc
264
+ raise B1Exception(
265
+ "Request failed after session retry"
266
+ ) from retry_exc
267
+ else:
268
+ exc_captured = e
269
+ sap_code, sap_msg = self._parse_sap_error(response)
270
+ # ── Raise specialised exception before falling back to B1Exception ──
271
+ try:
272
+ body = response.json() if response.content else None
273
+ except Exception:
274
+ body = None
275
+ self._raise_if_concurrency_error(
276
+ response.status_code, sap_code, sap_msg, endpoint_path, body
277
+ )
278
+ raise B1Exception(f"SAP Error {sap_code}: {sap_msg}") from e
279
+ except Exception as e:
280
+ exc_captured = e
281
+ raise B1Exception(f"Request failed: {e}") from e
282
+ finally:
283
+ duration_ms = (time.perf_counter() - start_time) * 1000
284
+ status_code = response.status_code if response is not None else None
285
+
286
+ ctx = HookContext(
287
+ req_id=req_id,
288
+ http_method=http_method,
289
+ base_url=self.raw_base_url,
290
+ endpoint=endpoint_path,
291
+ query_params=urlencode(ep_params) if ep_params else "",
292
+ db=self._db,
293
+ user=self._username,
294
+ status_code=status_code,
295
+ duration_ms=duration_ms,
296
+ extra=dict(self._obs.context_extras),
297
+ exc=exc_captured,
298
+ )
299
+
300
+ self._log_response(ctx)
301
+ self._hooks.dispatch(
302
+ "on_error" if exc_captured else "on_response", ctx, self._logger
303
+ )
304
+
305
+ if is_success and response is not None:
306
+ if response.content:
307
+ try:
308
+ data_out = response.json()
309
+ # ── ETag: extract from header (preferred) or body fallback ──
310
+ self._extract_etag(endpoint_path, dict(response.headers), data_out)
311
+ return Result(
312
+ status_code=response.status_code,
313
+ message=response.reason_phrase,
314
+ data=data_out,
315
+ next_link=data_out.get("odata.nextLink"),
316
+ next_params=self._get_ep_params(data_out.get("odata.nextLink"))
317
+ if data_out.get("odata.nextLink")
318
+ else None,
319
+ metadata=data_out.get("odata.metadata"),
320
+ )
321
+ except Exception:
322
+ raise B1Exception("Bad JSON response")
323
+ else:
324
+ self._extract_etag(endpoint_path, dict(response.headers), None)
325
+ return Result(
326
+ status_code=response.status_code, message=response.reason_phrase, data=None
327
+ )
328
+ else:
329
+ raise B1Exception(
330
+ f"HTTP Error {response.status_code if response else 'Unknown'}"
331
+ )
332
+
333
+ @handle_token()
334
+ def get(self, endpoint, ep_params=None, data=None):
335
+ """Execute a synchronous GET request."""
336
+ return self._do("GET", endpoint, ep_params, data)
337
+
338
+ @handle_token()
339
+ def post(self, endpoint, ep_params=None, data=None):
340
+ """Execute a synchronous POST request."""
341
+ return self._do("POST", endpoint, ep_params, data)
342
+
343
+ @handle_token()
344
+ def patch(self, endpoint, ep_params=None, data=None):
345
+ """Execute a synchronous PATCH request."""
346
+ return self._do("PATCH", endpoint, ep_params, data)
347
+
348
+ @handle_token()
349
+ def delete(self, endpoint, ep_params=None, data=None):
350
+ """Execute a synchronous DELETE request."""
351
+ return self._do("DELETE", endpoint, ep_params, data)
b1sl/b1sl/testing.py ADDED
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class B1TestHelper:
7
+ """
8
+ Test helper for SAP B1 environments.
9
+ Groups test_data logic away from the core runtime SDK.
10
+ """
11
+ def __init__(self, test_data: dict[str, Any]):
12
+ self.test_data = test_data
13
+
14
+ def get_test_item(self, profile: str = "simple") -> str:
15
+ """Retrieves a test item code from the environment test data."""
16
+ return self.test_data.get("items", {}).get(profile, "")
17
+
18
+ def get_test_customer(self, profile: str = "retail") -> str:
19
+ """Retrieves a test customer code from the environment test data."""
20
+ return self.test_data.get("customers", {}).get(profile, "")
21
+
22
+ def get_test_bp(self, profile: str = "simple") -> str:
23
+ """Retrieves a test Business Partner code from the environment test data."""
24
+ return self.test_data.get("business_partners", {}).get(profile, "")
File without changes
@@ -0,0 +1,60 @@
1
+ """
2
+ test_01_config.py — B1Config unit tests (pure pytest, no Django).
3
+
4
+ Renamed from test_01_settings.py which required Django settings.
5
+ """
6
+
7
+ from datetime import timedelta
8
+
9
+ import pytest
10
+
11
+ from b1sl.b1sl.config import B1Config
12
+
13
+
14
+ def test_config_rejects_empty_base_url():
15
+ with pytest.raises(ValueError, match="base_url"):
16
+ B1Config(base_url="", username="u", password="p", company_db="db")
17
+
18
+
19
+ def test_config_rejects_empty_credentials():
20
+ with pytest.raises(ValueError, match="credentials"):
21
+ B1Config(base_url="https://x", username="", password="", company_db="db")
22
+
23
+
24
+ def test_config_rejects_empty_company_db():
25
+ with pytest.raises(ValueError, match="company_db"):
26
+ B1Config(base_url="https://x", username="u", password="p", company_db="")
27
+
28
+
29
+ def test_config_defaults():
30
+ cfg = B1Config(base_url="https://x", username="u", password="p", company_db="db")
31
+ assert cfg.ssl_verify is True
32
+ assert cfg.reuse_token is True
33
+ assert cfg.token_timeout == timedelta(seconds=900)
34
+ assert cfg.max_page_size == 20
35
+ assert cfg.connect_timeout == 10.0
36
+ assert cfg.read_timeout == 60.0
37
+
38
+
39
+ def test_config_from_env_success():
40
+ with pytest.MonkeyPatch.context() as mp:
41
+ mp.setenv("SAPB1CLIENT_BASE_URL", "https://sap:50000/b1s/v1")
42
+ mp.setenv("SAPB1CLIENT_USERNAME", "admin")
43
+ mp.setenv("SAPB1CLIENT_PASSWORD", "pass")
44
+ mp.setenv("SAPB1CLIENT_COMPANY_DB", "MYDB")
45
+ cfg = B1Config.from_env()
46
+ assert cfg.base_url == "https://sap:50000/b1s/v1"
47
+ assert cfg.company_db == "MYDB"
48
+
49
+
50
+ def test_config_from_env_missing_raises():
51
+ with pytest.MonkeyPatch.context() as mp:
52
+ for key in (
53
+ "SAPB1CLIENT_BASE_URL",
54
+ "SAPB1CLIENT_USERNAME",
55
+ "SAPB1CLIENT_PASSWORD",
56
+ "SAPB1CLIENT_COMPANY_DB",
57
+ ):
58
+ mp.delenv(key, raising=False)
59
+ with pytest.raises(EnvironmentError):
60
+ B1Config.from_env()
@@ -0,0 +1,177 @@
1
+ """
2
+ test_02_rest_adapter.py — RestAdapter unit tests (pure pytest, no Django).
3
+
4
+ These are offline tests using mocks — no live SAP connection required.
5
+ Integration tests that require a live SAP instance belong in a separate
6
+ ``tests/integration/`` directory that is skipped in CI.
7
+ """
8
+
9
+ from datetime import timedelta
10
+ from json import JSONDecodeError
11
+ from unittest.mock import MagicMock, patch
12
+
13
+ import pytest
14
+
15
+ from b1sl.b1sl.config import B1Config
16
+ from b1sl.b1sl.rest_adapter import RestAdapter
17
+
18
+
19
+ @pytest.fixture
20
+ def config():
21
+ return B1Config(
22
+ base_url="https://sap:50000/b1s/v1",
23
+ username="manager",
24
+ password="s3cr3t",
25
+ company_db="TESTDB",
26
+ reuse_token=False,
27
+ connect_timeout=5.0,
28
+ read_timeout=30.0,
29
+ )
30
+
31
+
32
+ @pytest.fixture
33
+ def adapter(config):
34
+ return RestAdapter.from_config(config)
35
+
36
+
37
+ # ── Singleton removed ────────────────────────────────────────────────────── #
38
+
39
+
40
+ def test_no_singleton(config):
41
+ """Two from_config() calls must return two distinct objects."""
42
+ a = RestAdapter.from_config(config)
43
+ b = RestAdapter.from_config(config)
44
+ assert a is not b
45
+
46
+
47
+ # ── Constructor ──────────────────────────────────────────────────────────── #
48
+
49
+
50
+ def test_adapter_stores_config_fields(adapter, config):
51
+ assert adapter.url == config.base_url
52
+ assert adapter._username == config.username
53
+ assert adapter._connect_timeout == 5.0
54
+ assert adapter._read_timeout == 30.0
55
+
56
+
57
+ # ── Token expiry ─────────────────────────────────────────────────────────── #
58
+
59
+
60
+ def test_is_token_expire_when_no_expiry(adapter):
61
+ adapter.token_expiry = None
62
+ assert adapter._is_token_expire() is True
63
+
64
+
65
+ def test_is_token_expire_when_future(adapter):
66
+ from datetime import datetime
67
+
68
+ adapter.token_expiry = datetime.now() + timedelta(hours=1)
69
+ assert adapter._is_token_expire() is False
70
+
71
+
72
+ def test_is_token_expire_when_past(adapter):
73
+ from datetime import datetime
74
+
75
+ adapter.token_expiry = datetime.now() - timedelta(seconds=1)
76
+ assert adapter._is_token_expire() is True
77
+
78
+
79
+ # ── _parse_sap_error ─────────────────────────────────────────────────────── #
80
+
81
+
82
+ def _mock_resp(status, body=None, json_raises=False):
83
+ r = MagicMock()
84
+ r.status_code = status
85
+ r.reason = "Error"
86
+ if json_raises:
87
+ r.json.side_effect = JSONDecodeError("", "", 0)
88
+ else:
89
+ r.json.return_value = body
90
+ return r
91
+
92
+
93
+ @pytest.mark.parametrize(
94
+ "body,exp_code,exp_fragment",
95
+ [
96
+ (
97
+ {"error": {"code": -2028, "message": {"value": "Item does not exist"}}},
98
+ "-2028",
99
+ "Item does not exist",
100
+ ),
101
+ (
102
+ {"error": {"code": "-5002", "message": "No records found"}},
103
+ "-5002",
104
+ "No records found",
105
+ ),
106
+ (
107
+ {"error": {"code": 0, "message": {"lang": "en", "value": "Auth error"}}},
108
+ "0",
109
+ "Auth error",
110
+ ),
111
+ ({"detail": "not SAP format"}, "unknown", "HTTP 400"),
112
+ ],
113
+ )
114
+ def test_parse_sap_error_shapes(body, exp_code, exp_fragment):
115
+ r = _mock_resp(400, body)
116
+ code, msg = RestAdapter._parse_sap_error(r)
117
+ assert code == exp_code
118
+ assert exp_fragment in msg
119
+
120
+
121
+ def test_parse_sap_error_non_json():
122
+ r = _mock_resp(500, json_raises=True)
123
+ r.reason = "Internal Server Error"
124
+ code, msg = RestAdapter._parse_sap_error(r)
125
+ assert code == "unknown"
126
+ assert "500" in msg
127
+
128
+
129
+ # ── Password never appears in debug logs ─────────────────────────────────── #
130
+
131
+
132
+ def test_password_not_logged(adapter, caplog):
133
+ """The Password field must be redacted before any log statement in _do."""
134
+ import logging
135
+
136
+ with caplog.at_level(logging.DEBUG, logger="b1sl.b1sl.rest_adapter"):
137
+ with patch.object(adapter, "_execute_request") as mock_exec:
138
+ mock_resp = MagicMock()
139
+ mock_resp.status_code = 200
140
+ mock_resp.reason = "OK" # must be str for Result.message
141
+ mock_resp.content = b'{"SessionId":"abc","Version":"1","SessionTimeout":30}'
142
+ mock_resp.json.return_value = {
143
+ "SessionId": "abc",
144
+ "Version": "1",
145
+ "SessionTimeout": 30,
146
+ }
147
+ mock_resp.headers = {}
148
+ mock_resp.raise_for_status.return_value = None
149
+ mock_exec.return_value = mock_resp
150
+ adapter._do(
151
+ http_method="POST",
152
+ endpoint="/Login",
153
+ data={
154
+ "UserName": "manager",
155
+ "Password": "s3cr3t",
156
+ "CompanyDB": "TESTDB",
157
+ },
158
+ _is_login=True,
159
+ )
160
+ full_log = " ".join(caplog.messages)
161
+ assert "s3cr3t" not in full_log, "Raw password found in log output!"
162
+ assert "***" in full_log, "Password redaction marker '***' missing from log"
163
+
164
+
165
+ # ── Timeout passed to session.request ────────────────────────────────────── #
166
+
167
+
168
+ def test_execute_request_passes_timeout(adapter):
169
+ with patch.object(adapter.session, "request") as mock_req:
170
+ mock_resp = MagicMock()
171
+ mock_resp.status_code = 200
172
+ mock_resp.headers = {}
173
+ mock_req.return_value = mock_resp
174
+ adapter._execute_request("GET", "https://sap/b1s/v1/Items", {}, None, None)
175
+ _, kwargs = mock_req.call_args
176
+ assert "timeout" in kwargs
177
+ assert kwargs["timeout"] == (5.0, 30.0)