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.
- b1sl/__init__.py +5 -0
- b1sl/b1sl/__init__.py +56 -0
- b1sl/b1sl/_types.py +28 -0
- b1sl/b1sl/adapter.py +40 -0
- b1sl/b1sl/adapter_protocol.py +28 -0
- b1sl/b1sl/async_client.py +268 -0
- b1sl/b1sl/async_rest_adapter.py +347 -0
- b1sl/b1sl/base_adapter.py +394 -0
- b1sl/b1sl/client.py +112 -0
- b1sl/b1sl/config.py +138 -0
- b1sl/b1sl/config_manager.py +77 -0
- b1sl/b1sl/contrib/__init__.py +14 -0
- b1sl/b1sl/contrib/django/__init__.py +13 -0
- b1sl/b1sl/contrib/django/middleware/__init__.py +27 -0
- b1sl/b1sl/contrib/django/middleware/base.py +89 -0
- b1sl/b1sl/contrib/django/middleware/odata_decode_middleware.py +20 -0
- b1sl/b1sl/contrib/django/middleware/odata_transform_url_middleware.py +20 -0
- b1sl/b1sl/entities/__init__.py +2209 -0
- b1sl/b1sl/environment.py +36 -0
- b1sl/b1sl/exceptions/exceptions.py +142 -0
- b1sl/b1sl/fields/__init__.py +2020 -0
- b1sl/b1sl/fields/_generated/__init__.py +0 -0
- b1sl/b1sl/fields/_generated/complex_types.py +6926 -0
- b1sl/b1sl/fields/_generated/entities/__init__.py +0 -0
- b1sl/b1sl/fields/_generated/entities/businesspartners.py +544 -0
- b1sl/b1sl/fields/_generated/entities/finance.py +451 -0
- b1sl/b1sl/fields/_generated/entities/general.py +4587 -0
- b1sl/b1sl/fields/_generated/entities/inventory.py +761 -0
- b1sl/b1sl/fields/_generated/entities/production.py +256 -0
- b1sl/b1sl/fields/_generated/entities/purchasing.py +53 -0
- b1sl/b1sl/fields/_generated/entities/sales.py +244 -0
- b1sl/b1sl/logging_utils.py +119 -0
- b1sl/b1sl/models/__init__.py +13 -0
- b1sl/b1sl/models/_generated/__init__.py +0 -0
- b1sl/b1sl/models/_generated/_types.py +5 -0
- b1sl/b1sl/models/_generated/complex_types.py +6929 -0
- b1sl/b1sl/models/_generated/entities/__init__.py +581 -0
- b1sl/b1sl/models/_generated/entities/businesspartners.py +565 -0
- b1sl/b1sl/models/_generated/entities/finance.py +472 -0
- b1sl/b1sl/models/_generated/entities/general.py +4831 -0
- b1sl/b1sl/models/_generated/entities/inventory.py +786 -0
- b1sl/b1sl/models/_generated/entities/production.py +272 -0
- b1sl/b1sl/models/_generated/entities/purchasing.py +64 -0
- b1sl/b1sl/models/_generated/entities/sales.py +263 -0
- b1sl/b1sl/models/_generated/enums.py +3501 -0
- b1sl/b1sl/models/_generated/resources/__init__.py +0 -0
- b1sl/b1sl/models/_overrides/inventory.py +24 -0
- b1sl/b1sl/models/base.py +209 -0
- b1sl/b1sl/models/odata_query_model.py +50 -0
- b1sl/b1sl/models/paginated_result.py +85 -0
- b1sl/b1sl/models/result.py +17 -0
- b1sl/b1sl/resources/__init__.py +11 -0
- b1sl/b1sl/resources/_generated/__init__.py +0 -0
- b1sl/b1sl/resources/_generated/account_category.py +15 -0
- b1sl/b1sl/resources/_generated/account_category_service.py +21 -0
- b1sl/b1sl/resources/_generated/account_segmentation_categories.py +15 -0
- b1sl/b1sl/resources/_generated/account_segmentations.py +15 -0
- b1sl/b1sl/resources/_generated/accounts_service.py +33 -0
- b1sl/b1sl/resources/_generated/accrual_types.py +15 -0
- b1sl/b1sl/resources/_generated/accrual_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/activities.py +15 -0
- b1sl/b1sl/resources/_generated/activities_service.py +109 -0
- b1sl/b1sl/resources/_generated/activity_locations.py +15 -0
- b1sl/b1sl/resources/_generated/activity_recipient_lists.py +15 -0
- b1sl/b1sl/resources/_generated/activity_recipient_lists_service.py +21 -0
- b1sl/b1sl/resources/_generated/activity_statuses.py +15 -0
- b1sl/b1sl/resources/_generated/activity_types.py +15 -0
- b1sl/b1sl/resources/_generated/additional_expenses.py +15 -0
- b1sl/b1sl/resources/_generated/address_service.py +38 -0
- b1sl/b1sl/resources/_generated/alert_managements.py +16 -0
- b1sl/b1sl/resources/_generated/alternate_cat_num.py +15 -0
- b1sl/b1sl/resources/_generated/alternative_items_service.py +113 -0
- b1sl/b1sl/resources/_generated/approval_requests.py +15 -0
- b1sl/b1sl/resources/_generated/approval_requests_service.py +33 -0
- b1sl/b1sl/resources/_generated/approval_stages.py +15 -0
- b1sl/b1sl/resources/_generated/approval_stages_service.py +21 -0
- b1sl/b1sl/resources/_generated/approval_templates.py +15 -0
- b1sl/b1sl/resources/_generated/approval_templates_service.py +21 -0
- b1sl/b1sl/resources/_generated/asset_capitalization.py +15 -0
- b1sl/b1sl/resources/_generated/asset_capitalization_credit_memo.py +15 -0
- b1sl/b1sl/resources/_generated/asset_capitalization_credit_memo_service.py +37 -0
- b1sl/b1sl/resources/_generated/asset_capitalization_service.py +37 -0
- b1sl/b1sl/resources/_generated/asset_classes.py +15 -0
- b1sl/b1sl/resources/_generated/asset_classes_service.py +21 -0
- b1sl/b1sl/resources/_generated/asset_depreciation_groups.py +15 -0
- b1sl/b1sl/resources/_generated/asset_depreciation_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/asset_groups.py +15 -0
- b1sl/b1sl/resources/_generated/asset_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/asset_manual_depreciation.py +15 -0
- b1sl/b1sl/resources/_generated/asset_manual_depreciation_service.py +37 -0
- b1sl/b1sl/resources/_generated/asset_retirement.py +15 -0
- b1sl/b1sl/resources/_generated/asset_retirement_service.py +37 -0
- b1sl/b1sl/resources/_generated/asset_transfer.py +15 -0
- b1sl/b1sl/resources/_generated/asset_transfer_service.py +37 -0
- b1sl/b1sl/resources/_generated/attachments2.py +15 -0
- b1sl/b1sl/resources/_generated/attribute_groups.py +15 -0
- b1sl/b1sl/resources/_generated/attribute_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/bank_charges_allocation_codes.py +15 -0
- b1sl/b1sl/resources/_generated/bank_charges_allocation_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/bank_pages.py +15 -0
- b1sl/b1sl/resources/_generated/bank_statements.py +15 -0
- b1sl/b1sl/resources/_generated/bank_statements_service.py +32 -0
- b1sl/b1sl/resources/_generated/banks.py +15 -0
- b1sl/b1sl/resources/_generated/bar_codes.py +15 -0
- b1sl/b1sl/resources/_generated/bar_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/batch_number_details.py +15 -0
- b1sl/b1sl/resources/_generated/bill_of_exchange_transactions.py +15 -0
- b1sl/b1sl/resources/_generated/bin_location_attributes.py +15 -0
- b1sl/b1sl/resources/_generated/bin_location_attributes_service.py +21 -0
- b1sl/b1sl/resources/_generated/bin_location_fields.py +15 -0
- b1sl/b1sl/resources/_generated/bin_location_fields_service.py +21 -0
- b1sl/b1sl/resources/_generated/bin_locations.py +15 -0
- b1sl/b1sl/resources/_generated/bin_locations_service.py +21 -0
- b1sl/b1sl/resources/_generated/blanket_agreements.py +15 -0
- b1sl/b1sl/resources/_generated/blanket_agreements_service.py +21 -0
- b1sl/b1sl/resources/_generated/boe_document_types.py +15 -0
- b1sl/b1sl/resources/_generated/boe_document_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/boe_instructions.py +15 -0
- b1sl/b1sl/resources/_generated/boe_instructions_service.py +21 -0
- b1sl/b1sl/resources/_generated/boe_lines_service.py +30 -0
- b1sl/b1sl/resources/_generated/boe_portfolios.py +15 -0
- b1sl/b1sl/resources/_generated/boe_portfolios_service.py +21 -0
- b1sl/b1sl/resources/_generated/bp_fiscal_registry_id.py +15 -0
- b1sl/b1sl/resources/_generated/bp_opening_balance_service.py +41 -0
- b1sl/b1sl/resources/_generated/bp_priorities.py +15 -0
- b1sl/b1sl/resources/_generated/branches.py +15 -0
- b1sl/b1sl/resources/_generated/branches_service.py +21 -0
- b1sl/b1sl/resources/_generated/brazil_beverage_indexers.py +15 -0
- b1sl/b1sl/resources/_generated/brazil_beverage_indexers_service.py +21 -0
- b1sl/b1sl/resources/_generated/brazil_fuel_indexers.py +15 -0
- b1sl/b1sl/resources/_generated/brazil_fuel_indexers_service.py +21 -0
- b1sl/b1sl/resources/_generated/brazil_multi_indexers.py +15 -0
- b1sl/b1sl/resources/_generated/brazil_numeric_indexers.py +15 -0
- b1sl/b1sl/resources/_generated/brazil_string_indexers.py +15 -0
- b1sl/b1sl/resources/_generated/budget_distributions.py +15 -0
- b1sl/b1sl/resources/_generated/budget_scenarios.py +15 -0
- b1sl/b1sl/resources/_generated/budgets.py +15 -0
- b1sl/b1sl/resources/_generated/business_partner_groups.py +15 -0
- b1sl/b1sl/resources/_generated/business_partner_properties.py +15 -0
- b1sl/b1sl/resources/_generated/business_partner_properties_service.py +21 -0
- b1sl/b1sl/resources/_generated/business_partners.py +15 -0
- b1sl/b1sl/resources/_generated/business_partners_service.py +47 -0
- b1sl/b1sl/resources/_generated/business_places.py +15 -0
- b1sl/b1sl/resources/_generated/campaign_response_type.py +15 -0
- b1sl/b1sl/resources/_generated/campaign_response_type_service.py +21 -0
- b1sl/b1sl/resources/_generated/campaigns.py +15 -0
- b1sl/b1sl/resources/_generated/campaigns_service.py +21 -0
- b1sl/b1sl/resources/_generated/cash_discounts.py +15 -0
- b1sl/b1sl/resources/_generated/cash_discounts_service.py +21 -0
- b1sl/b1sl/resources/_generated/cash_flow_line_items.py +15 -0
- b1sl/b1sl/resources/_generated/cash_flow_line_items_service.py +21 -0
- b1sl/b1sl/resources/_generated/certificate_series.py +15 -0
- b1sl/b1sl/resources/_generated/certificate_series_service.py +21 -0
- b1sl/b1sl/resources/_generated/change_logs_service.py +51 -0
- b1sl/b1sl/resources/_generated/chart_of_accounts.py +15 -0
- b1sl/b1sl/resources/_generated/check_lines_service.py +38 -0
- b1sl/b1sl/resources/_generated/checksfor_payment.py +15 -0
- b1sl/b1sl/resources/_generated/choose_from_list.py +15 -0
- b1sl/b1sl/resources/_generated/client_mixin.py +3920 -0
- b1sl/b1sl/resources/_generated/closing_date_procedure.py +15 -0
- b1sl/b1sl/resources/_generated/cockpits.py +15 -0
- b1sl/b1sl/resources/_generated/cockpits_service.py +46 -0
- b1sl/b1sl/resources/_generated/commission_groups.py +15 -0
- b1sl/b1sl/resources/_generated/company_service.py +410 -0
- b1sl/b1sl/resources/_generated/contacts.py +15 -0
- b1sl/b1sl/resources/_generated/contract_templates.py +15 -0
- b1sl/b1sl/resources/_generated/correction_invoice.py +15 -0
- b1sl/b1sl/resources/_generated/correction_invoice_reversal.py +15 -0
- b1sl/b1sl/resources/_generated/correction_invoice_reversal_service.py +65 -0
- b1sl/b1sl/resources/_generated/correction_invoice_service.py +78 -0
- b1sl/b1sl/resources/_generated/correction_purchase_invoice.py +15 -0
- b1sl/b1sl/resources/_generated/correction_purchase_invoice_reversal.py +15 -0
- b1sl/b1sl/resources/_generated/correction_purchase_invoice_reversal_service.py +65 -0
- b1sl/b1sl/resources/_generated/correction_purchase_invoice_service.py +78 -0
- b1sl/b1sl/resources/_generated/cost_center_types.py +15 -0
- b1sl/b1sl/resources/_generated/cost_center_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/cost_element_service.py +21 -0
- b1sl/b1sl/resources/_generated/cost_elements.py +15 -0
- b1sl/b1sl/resources/_generated/counties.py +15 -0
- b1sl/b1sl/resources/_generated/counties_service.py +21 -0
- b1sl/b1sl/resources/_generated/countries.py +15 -0
- b1sl/b1sl/resources/_generated/countries_service.py +21 -0
- b1sl/b1sl/resources/_generated/credit_card_payments.py +15 -0
- b1sl/b1sl/resources/_generated/credit_cards.py +15 -0
- b1sl/b1sl/resources/_generated/credit_lines_service.py +38 -0
- b1sl/b1sl/resources/_generated/credit_notes.py +15 -0
- b1sl/b1sl/resources/_generated/credit_notes_service.py +100 -0
- b1sl/b1sl/resources/_generated/credit_payment_methods.py +15 -0
- b1sl/b1sl/resources/_generated/currencies.py +15 -0
- b1sl/b1sl/resources/_generated/customer_equipment_cards.py +15 -0
- b1sl/b1sl/resources/_generated/customs_declaration.py +15 -0
- b1sl/b1sl/resources/_generated/customs_groups.py +15 -0
- b1sl/b1sl/resources/_generated/cycle_count_determinations.py +15 -0
- b1sl/b1sl/resources/_generated/cycle_count_determinations_service.py +21 -0
- b1sl/b1sl/resources/_generated/dashboard_packages_service.py +28 -0
- b1sl/b1sl/resources/_generated/deductible_tax_service.py +21 -0
- b1sl/b1sl/resources/_generated/deductible_taxes.py +15 -0
- b1sl/b1sl/resources/_generated/deduction_tax_groups.py +15 -0
- b1sl/b1sl/resources/_generated/deduction_tax_hierarchies.py +15 -0
- b1sl/b1sl/resources/_generated/deduction_tax_sub_groups.py +15 -0
- b1sl/b1sl/resources/_generated/deduction_tax_sub_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/delivery_notes.py +15 -0
- b1sl/b1sl/resources/_generated/delivery_notes_service.py +85 -0
- b1sl/b1sl/resources/_generated/departments.py +15 -0
- b1sl/b1sl/resources/_generated/departments_service.py +21 -0
- b1sl/b1sl/resources/_generated/deposits.py +15 -0
- b1sl/b1sl/resources/_generated/deposits_service.py +53 -0
- b1sl/b1sl/resources/_generated/depreciation_areas.py +15 -0
- b1sl/b1sl/resources/_generated/depreciation_areas_service.py +21 -0
- b1sl/b1sl/resources/_generated/depreciation_type_pools.py +15 -0
- b1sl/b1sl/resources/_generated/depreciation_type_pools_service.py +21 -0
- b1sl/b1sl/resources/_generated/depreciation_types.py +15 -0
- b1sl/b1sl/resources/_generated/depreciation_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/determination_criterias.py +15 -0
- b1sl/b1sl/resources/_generated/determination_criterias_service.py +21 -0
- b1sl/b1sl/resources/_generated/dimensions.py +15 -0
- b1sl/b1sl/resources/_generated/dimensions_service.py +21 -0
- b1sl/b1sl/resources/_generated/distribution_rules.py +15 -0
- b1sl/b1sl/resources/_generated/distribution_rules_service.py +21 -0
- b1sl/b1sl/resources/_generated/dnf_code_setup.py +15 -0
- b1sl/b1sl/resources/_generated/dnf_code_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/down_payments.py +15 -0
- b1sl/b1sl/resources/_generated/down_payments_service.py +68 -0
- b1sl/b1sl/resources/_generated/drafts.py +15 -0
- b1sl/b1sl/resources/_generated/drafts_service.py +86 -0
- b1sl/b1sl/resources/_generated/dunning_letters.py +15 -0
- b1sl/b1sl/resources/_generated/dunning_terms.py +15 -0
- b1sl/b1sl/resources/_generated/dunning_terms_service.py +21 -0
- b1sl/b1sl/resources/_generated/dynamic_system_strings.py +15 -0
- b1sl/b1sl/resources/_generated/electronic_communication_action_service.py +122 -0
- b1sl/b1sl/resources/_generated/electronic_communication_actions_service.py +119 -0
- b1sl/b1sl/resources/_generated/electronic_file_formats.py +15 -0
- b1sl/b1sl/resources/_generated/electronic_file_formats_service.py +21 -0
- b1sl/b1sl/resources/_generated/email_groups.py +15 -0
- b1sl/b1sl/resources/_generated/email_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/employee_id_type.py +15 -0
- b1sl/b1sl/resources/_generated/employee_id_type_service.py +21 -0
- b1sl/b1sl/resources/_generated/employee_position.py +15 -0
- b1sl/b1sl/resources/_generated/employee_position_service.py +21 -0
- b1sl/b1sl/resources/_generated/employee_roles_setup.py +15 -0
- b1sl/b1sl/resources/_generated/employee_roles_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/employee_status.py +15 -0
- b1sl/b1sl/resources/_generated/employee_status_service.py +21 -0
- b1sl/b1sl/resources/_generated/employee_transfers.py +15 -0
- b1sl/b1sl/resources/_generated/employee_transfers_service.py +21 -0
- b1sl/b1sl/resources/_generated/employees_info.py +15 -0
- b1sl/b1sl/resources/_generated/employment_category_service.py +21 -0
- b1sl/b1sl/resources/_generated/employment_categorys.py +15 -0
- b1sl/b1sl/resources/_generated/enhanced_discount_groups.py +15 -0
- b1sl/b1sl/resources/_generated/enhanced_discount_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/exceptional_event_service.py +21 -0
- b1sl/b1sl/resources/_generated/exceptional_events.py +15 -0
- b1sl/b1sl/resources/_generated/extended_translations.py +15 -0
- b1sl/b1sl/resources/_generated/extended_translations_service.py +21 -0
- b1sl/b1sl/resources/_generated/external_calls_service.py +54 -0
- b1sl/b1sl/resources/_generated/external_reconciliations_service.py +104 -0
- b1sl/b1sl/resources/_generated/fa_account_determinations.py +15 -0
- b1sl/b1sl/resources/_generated/fa_account_determinations_service.py +21 -0
- b1sl/b1sl/resources/_generated/factoring_indicators.py +15 -0
- b1sl/b1sl/resources/_generated/financial_years.py +15 -0
- b1sl/b1sl/resources/_generated/financial_years_service.py +21 -0
- b1sl/b1sl/resources/_generated/fiscal_printer.py +15 -0
- b1sl/b1sl/resources/_generated/fiscal_printer_service.py +21 -0
- b1sl/b1sl/resources/_generated/fixed_asset_items_service.py +55 -0
- b1sl/b1sl/resources/_generated/form_preferences.py +15 -0
- b1sl/b1sl/resources/_generated/formatted_searches.py +15 -0
- b1sl/b1sl/resources/_generated/forms1099.py +15 -0
- b1sl/b1sl/resources/_generated/gl_account_advanced_rules.py +15 -0
- b1sl/b1sl/resources/_generated/gl_account_advanced_rules_service.py +21 -0
- b1sl/b1sl/resources/_generated/goods_return_request.py +15 -0
- b1sl/b1sl/resources/_generated/goods_return_request_service.py +69 -0
- b1sl/b1sl/resources/_generated/gov_pay_codes.py +15 -0
- b1sl/b1sl/resources/_generated/gov_pay_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/gt_is_service.py +28 -0
- b1sl/b1sl/resources/_generated/holidays.py +15 -0
- b1sl/b1sl/resources/_generated/house_bank_accounts.py +15 -0
- b1sl/b1sl/resources/_generated/incoming_payments.py +15 -0
- b1sl/b1sl/resources/_generated/india_hsn.py +15 -0
- b1sl/b1sl/resources/_generated/india_hsn_service.py +21 -0
- b1sl/b1sl/resources/_generated/industries.py +15 -0
- b1sl/b1sl/resources/_generated/integration_packages_configure.py +15 -0
- b1sl/b1sl/resources/_generated/integration_packages_configure_service.py +21 -0
- b1sl/b1sl/resources/_generated/internal_reconciliations.py +15 -0
- b1sl/b1sl/resources/_generated/internal_reconciliations_service.py +51 -0
- b1sl/b1sl/resources/_generated/intrastat_configuration.py +15 -0
- b1sl/b1sl/resources/_generated/intrastat_configuration_service.py +21 -0
- b1sl/b1sl/resources/_generated/inventory_countings.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_countings_service.py +21 -0
- b1sl/b1sl/resources/_generated/inventory_cycles.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_gen_entries.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_gen_entry_service.py +67 -0
- b1sl/b1sl/resources/_generated/inventory_gen_exit_service.py +66 -0
- b1sl/b1sl/resources/_generated/inventory_gen_exits.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_opening_balances.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_opening_balances_service.py +21 -0
- b1sl/b1sl/resources/_generated/inventory_postings.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_postings_service.py +34 -0
- b1sl/b1sl/resources/_generated/inventory_transfer_requests.py +15 -0
- b1sl/b1sl/resources/_generated/inventory_transfer_requests_service.py +43 -0
- b1sl/b1sl/resources/_generated/invoices.py +15 -0
- b1sl/b1sl/resources/_generated/invoices_service.py +100 -0
- b1sl/b1sl/resources/_generated/item_groups.py +15 -0
- b1sl/b1sl/resources/_generated/item_properties.py +15 -0
- b1sl/b1sl/resources/_generated/items.py +15 -0
- b1sl/b1sl/resources/_generated/journal_entries.py +15 -0
- b1sl/b1sl/resources/_generated/journal_entry_document_type_service.py +21 -0
- b1sl/b1sl/resources/_generated/journal_entry_document_types.py +15 -0
- b1sl/b1sl/resources/_generated/journal_vouchers_service.py +45 -0
- b1sl/b1sl/resources/_generated/knowledge_base_solutions.py +15 -0
- b1sl/b1sl/resources/_generated/kp_is.py +15 -0
- b1sl/b1sl/resources/_generated/kp_is_service.py +21 -0
- b1sl/b1sl/resources/_generated/landed_costs.py +15 -0
- b1sl/b1sl/resources/_generated/landed_costs_codes.py +15 -0
- b1sl/b1sl/resources/_generated/landed_costs_service.py +21 -0
- b1sl/b1sl/resources/_generated/legal_data.py +15 -0
- b1sl/b1sl/resources/_generated/length_measures.py +15 -0
- b1sl/b1sl/resources/_generated/license_service.py +21 -0
- b1sl/b1sl/resources/_generated/local_era.py +15 -0
- b1sl/b1sl/resources/_generated/manufacturers.py +15 -0
- b1sl/b1sl/resources/_generated/material_groups.py +15 -0
- b1sl/b1sl/resources/_generated/material_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/material_revaluation.py +15 -0
- b1sl/b1sl/resources/_generated/material_revaluation_fifo_service.py +33 -0
- b1sl/b1sl/resources/_generated/material_revaluation_snb_service.py +33 -0
- b1sl/b1sl/resources/_generated/messages.py +15 -0
- b1sl/b1sl/resources/_generated/messages_service.py +35 -0
- b1sl/b1sl/resources/_generated/mobile_add_on_setting.py +15 -0
- b1sl/b1sl/resources/_generated/mobile_add_on_setting_service.py +21 -0
- b1sl/b1sl/resources/_generated/mobile_app_service.py +240 -0
- b1sl/b1sl/resources/_generated/multi_language_translations.py +15 -0
- b1sl/b1sl/resources/_generated/nature_of_assessees.py +15 -0
- b1sl/b1sl/resources/_generated/nature_of_assessees_service.py +21 -0
- b1sl/b1sl/resources/_generated/ncm_codes_setup.py +15 -0
- b1sl/b1sl/resources/_generated/ncm_codes_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/nf_models.py +15 -0
- b1sl/b1sl/resources/_generated/nf_models_service.py +21 -0
- b1sl/b1sl/resources/_generated/nf_tax_categories.py +15 -0
- b1sl/b1sl/resources/_generated/nf_tax_categories_service.py +21 -0
- b1sl/b1sl/resources/_generated/nota_fiscal_cfop.py +15 -0
- b1sl/b1sl/resources/_generated/nota_fiscal_cst.py +15 -0
- b1sl/b1sl/resources/_generated/nota_fiscal_usage.py +15 -0
- b1sl/b1sl/resources/_generated/occurrence_codes.py +15 -0
- b1sl/b1sl/resources/_generated/occurrence_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/orders.py +15 -0
- b1sl/b1sl/resources/_generated/orders_service.py +94 -0
- b1sl/b1sl/resources/_generated/packages_types.py +15 -0
- b1sl/b1sl/resources/_generated/partners_setups.py +15 -0
- b1sl/b1sl/resources/_generated/partners_setups_service.py +21 -0
- b1sl/b1sl/resources/_generated/payment_blocks.py +15 -0
- b1sl/b1sl/resources/_generated/payment_blocks_service.py +21 -0
- b1sl/b1sl/resources/_generated/payment_calculation_service.py +37 -0
- b1sl/b1sl/resources/_generated/payment_drafts.py +15 -0
- b1sl/b1sl/resources/_generated/payment_reason_code_service.py +21 -0
- b1sl/b1sl/resources/_generated/payment_reason_codes.py +15 -0
- b1sl/b1sl/resources/_generated/payment_run_export.py +15 -0
- b1sl/b1sl/resources/_generated/payment_terms_types.py +15 -0
- b1sl/b1sl/resources/_generated/payment_terms_types_service.py +28 -0
- b1sl/b1sl/resources/_generated/pick_lists.py +15 -0
- b1sl/b1sl/resources/_generated/pick_lists_service.py +78 -0
- b1sl/b1sl/resources/_generated/pos_daily_summary.py +15 -0
- b1sl/b1sl/resources/_generated/predefined_texts.py +15 -0
- b1sl/b1sl/resources/_generated/predefined_texts_service.py +21 -0
- b1sl/b1sl/resources/_generated/price_lists.py +15 -0
- b1sl/b1sl/resources/_generated/product_trees.py +15 -0
- b1sl/b1sl/resources/_generated/production_orders.py +15 -0
- b1sl/b1sl/resources/_generated/profit_centers.py +15 -0
- b1sl/b1sl/resources/_generated/profit_centers_service.py +21 -0
- b1sl/b1sl/resources/_generated/project_management_configuration_service.py +333 -0
- b1sl/b1sl/resources/_generated/project_management_service.py +104 -0
- b1sl/b1sl/resources/_generated/project_management_time_sheet.py +15 -0
- b1sl/b1sl/resources/_generated/project_managements.py +15 -0
- b1sl/b1sl/resources/_generated/projects.py +15 -0
- b1sl/b1sl/resources/_generated/projects_service.py +21 -0
- b1sl/b1sl/resources/_generated/purchase_credit_notes.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_credit_notes_service.py +85 -0
- b1sl/b1sl/resources/_generated/purchase_delivery_notes.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_delivery_notes_service.py +85 -0
- b1sl/b1sl/resources/_generated/purchase_down_payments.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_down_payments_service.py +70 -0
- b1sl/b1sl/resources/_generated/purchase_invoices.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_invoices_service.py +85 -0
- b1sl/b1sl/resources/_generated/purchase_orders.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_orders_service.py +69 -0
- b1sl/b1sl/resources/_generated/purchase_quotations.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_quotations_service.py +70 -0
- b1sl/b1sl/resources/_generated/purchase_request_service.py +68 -0
- b1sl/b1sl/resources/_generated/purchase_requests.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_returns.py +15 -0
- b1sl/b1sl/resources/_generated/purchase_returns_service.py +85 -0
- b1sl/b1sl/resources/_generated/purchase_tax_invoices.py +15 -0
- b1sl/b1sl/resources/_generated/qr_code_service.py +33 -0
- b1sl/b1sl/resources/_generated/query_auth_groups.py +15 -0
- b1sl/b1sl/resources/_generated/query_categories.py +15 -0
- b1sl/b1sl/resources/_generated/query_service.py +29 -0
- b1sl/b1sl/resources/_generated/queue.py +15 -0
- b1sl/b1sl/resources/_generated/quotations.py +15 -0
- b1sl/b1sl/resources/_generated/quotations_service.py +69 -0
- b1sl/b1sl/resources/_generated/recurring_transaction_service.py +67 -0
- b1sl/b1sl/resources/_generated/relationships.py +15 -0
- b1sl/b1sl/resources/_generated/report_filter.py +15 -0
- b1sl/b1sl/resources/_generated/report_filter_service.py +30 -0
- b1sl/b1sl/resources/_generated/report_layouts_service.py +237 -0
- b1sl/b1sl/resources/_generated/report_types.py +15 -0
- b1sl/b1sl/resources/_generated/report_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/resource_capacities.py +15 -0
- b1sl/b1sl/resources/_generated/resource_capacities_service.py +34 -0
- b1sl/b1sl/resources/_generated/resource_groups.py +15 -0
- b1sl/b1sl/resources/_generated/resource_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/resource_properties.py +15 -0
- b1sl/b1sl/resources/_generated/resource_properties_service.py +21 -0
- b1sl/b1sl/resources/_generated/resources.py +15 -0
- b1sl/b1sl/resources/_generated/resources_service.py +21 -0
- b1sl/b1sl/resources/_generated/retorno_codes.py +15 -0
- b1sl/b1sl/resources/_generated/retorno_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/return_request.py +15 -0
- b1sl/b1sl/resources/_generated/return_request_service.py +69 -0
- b1sl/b1sl/resources/_generated/returns.py +15 -0
- b1sl/b1sl/resources/_generated/returns_service.py +85 -0
- b1sl/b1sl/resources/_generated/route_stages.py +15 -0
- b1sl/b1sl/resources/_generated/route_stages_service.py +21 -0
- b1sl/b1sl/resources/_generated/sales_forecast.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunities.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_competitors_setup.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_competitors_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_interests_setup.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_interests_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_reasons_setup.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_reasons_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_sources_setup.py +15 -0
- b1sl/b1sl/resources/_generated/sales_opportunity_sources_setup_service.py +21 -0
- b1sl/b1sl/resources/_generated/sales_persons.py +15 -0
- b1sl/b1sl/resources/_generated/sales_stages.py +15 -0
- b1sl/b1sl/resources/_generated/sales_tax_authorities.py +15 -0
- b1sl/b1sl/resources/_generated/sales_tax_authorities_types.py +15 -0
- b1sl/b1sl/resources/_generated/sales_tax_codes.py +15 -0
- b1sl/b1sl/resources/_generated/sales_tax_invoices.py +15 -0
- b1sl/b1sl/resources/_generated/sbo_bob_service.py +89 -0
- b1sl/b1sl/resources/_generated/sections.py +15 -0
- b1sl/b1sl/resources/_generated/sections_service.py +21 -0
- b1sl/b1sl/resources/_generated/self_credit_memo_service.py +69 -0
- b1sl/b1sl/resources/_generated/self_credit_memos.py +15 -0
- b1sl/b1sl/resources/_generated/self_invoice_service.py +69 -0
- b1sl/b1sl/resources/_generated/self_invoices.py +15 -0
- b1sl/b1sl/resources/_generated/serial_number_details.py +15 -0
- b1sl/b1sl/resources/_generated/series_service.py +305 -0
- b1sl/b1sl/resources/_generated/service_call_origins.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_origins_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_call_problem_sub_types.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_problem_sub_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_call_problem_types.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_problem_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_call_solution_status.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_solution_status_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_call_status.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_status_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_call_types.py +15 -0
- b1sl/b1sl/resources/_generated/service_call_types_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_calls.py +15 -0
- b1sl/b1sl/resources/_generated/service_contracts.py +15 -0
- b1sl/b1sl/resources/_generated/service_groups.py +15 -0
- b1sl/b1sl/resources/_generated/service_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/service_tax_posting_service.py +36 -0
- b1sl/b1sl/resources/_generated/shipping_types.py +15 -0
- b1sl/b1sl/resources/_generated/short_link_mappings_service.py +26 -0
- b1sl/b1sl/resources/_generated/special_prices.py +15 -0
- b1sl/b1sl/resources/_generated/states.py +15 -0
- b1sl/b1sl/resources/_generated/states_service.py +21 -0
- b1sl/b1sl/resources/_generated/stock_takings.py +15 -0
- b1sl/b1sl/resources/_generated/stock_transfer_draft_service.py +34 -0
- b1sl/b1sl/resources/_generated/stock_transfer_drafts.py +15 -0
- b1sl/b1sl/resources/_generated/stock_transfer_service.py +43 -0
- b1sl/b1sl/resources/_generated/stock_transfers.py +15 -0
- b1sl/b1sl/resources/_generated/target_groups.py +15 -0
- b1sl/b1sl/resources/_generated/target_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/tax_code_determinations.py +15 -0
- b1sl/b1sl/resources/_generated/tax_code_determinations_service.py +21 -0
- b1sl/b1sl/resources/_generated/tax_code_determinations_tcd.py +15 -0
- b1sl/b1sl/resources/_generated/tax_code_determinations_tcd_service.py +21 -0
- b1sl/b1sl/resources/_generated/tax_invoice_report.py +15 -0
- b1sl/b1sl/resources/_generated/tax_web_sites.py +15 -0
- b1sl/b1sl/resources/_generated/tax_web_sites_service.py +27 -0
- b1sl/b1sl/resources/_generated/teams.py +15 -0
- b1sl/b1sl/resources/_generated/termination_reason.py +15 -0
- b1sl/b1sl/resources/_generated/termination_reason_service.py +21 -0
- b1sl/b1sl/resources/_generated/territories.py +15 -0
- b1sl/b1sl/resources/_generated/tracking_notes.py +15 -0
- b1sl/b1sl/resources/_generated/tracking_notes_service.py +21 -0
- b1sl/b1sl/resources/_generated/transaction_codes.py +15 -0
- b1sl/b1sl/resources/_generated/transaction_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/transportation_document.py +15 -0
- b1sl/b1sl/resources/_generated/tsr_exceptional_event_service.py +21 -0
- b1sl/b1sl/resources/_generated/tsr_exceptional_events.py +15 -0
- b1sl/b1sl/resources/_generated/unit_of_measurement_groups.py +15 -0
- b1sl/b1sl/resources/_generated/unit_of_measurement_groups_service.py +21 -0
- b1sl/b1sl/resources/_generated/unit_of_measurements.py +15 -0
- b1sl/b1sl/resources/_generated/unit_of_measurements_service.py +21 -0
- b1sl/b1sl/resources/_generated/user_default_groups.py +15 -0
- b1sl/b1sl/resources/_generated/user_fields_md.py +15 -0
- b1sl/b1sl/resources/_generated/user_keys_md.py +15 -0
- b1sl/b1sl/resources/_generated/user_languages.py +15 -0
- b1sl/b1sl/resources/_generated/user_menu_service.py +103 -0
- b1sl/b1sl/resources/_generated/user_objects_md.py +15 -0
- b1sl/b1sl/resources/_generated/user_permission_tree.py +15 -0
- b1sl/b1sl/resources/_generated/user_queries.py +15 -0
- b1sl/b1sl/resources/_generated/user_tables_md.py +15 -0
- b1sl/b1sl/resources/_generated/users.py +15 -0
- b1sl/b1sl/resources/_generated/users_service.py +21 -0
- b1sl/b1sl/resources/_generated/value_mapping.py +15 -0
- b1sl/b1sl/resources/_generated/value_mapping_communication.py +15 -0
- b1sl/b1sl/resources/_generated/value_mapping_service.py +64 -0
- b1sl/b1sl/resources/_generated/vat_groups.py +15 -0
- b1sl/b1sl/resources/_generated/vendor_payments.py +15 -0
- b1sl/b1sl/resources/_generated/w_tax_type_code_service.py +21 -0
- b1sl/b1sl/resources/_generated/w_tax_type_codes.py +15 -0
- b1sl/b1sl/resources/_generated/warehouse_locations.py +15 -0
- b1sl/b1sl/resources/_generated/warehouse_sublevel_codes.py +15 -0
- b1sl/b1sl/resources/_generated/warehouse_sublevel_codes_service.py +21 -0
- b1sl/b1sl/resources/_generated/warehouses.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_bookmark_tile_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_bookmark_tiles.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_dashboard_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_dashboards.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_form_setting_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_form_settings.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_launchpad_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_launchpads.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_listview_filter_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_listview_filters.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_notification_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_notifications.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_preference_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_preferences.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_recent_activities.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_recent_activity_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_variant_group_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_variant_groups.py +15 -0
- b1sl/b1sl/resources/_generated/web_client_variant_service.py +21 -0
- b1sl/b1sl/resources/_generated/web_client_variants.py +15 -0
- b1sl/b1sl/resources/_generated/weight_measures.py +15 -0
- b1sl/b1sl/resources/_generated/withholding_tax_codes.py +15 -0
- b1sl/b1sl/resources/_generated/witholding_tax_definition.py +15 -0
- b1sl/b1sl/resources/_generated/wizard_payment_methods.py +15 -0
- b1sl/b1sl/resources/_generated/workflow_task_service.py +47 -0
- b1sl/b1sl/resources/async_base.py +60 -0
- b1sl/b1sl/resources/base.py +159 -0
- b1sl/b1sl/resources/udo.py +78 -0
- b1sl/b1sl/rest_adapter.py +351 -0
- b1sl/b1sl/testing.py +24 -0
- b1sl/b1sl/tests/__init__.py +0 -0
- b1sl/b1sl/tests/test_01_settings.py +60 -0
- b1sl/b1sl/tests/test_02_rest_adapter.py +177 -0
- b1sl/b1sl/tests/test_03_items_endpoint.py +172 -0
- b1sl/b1sl/tests/test_04_serialnumberdetails_endpoint.py +181 -0
- b1sl/b1sl/tests/test_observability.py +153 -0
- b1sl/b1sl/tests/test_utils.py +24 -0
- b1sl/contrib/__init__.py +0 -0
- b1sl/contrib/django/__init__.py +15 -0
- b1sl/contrib/django/base.py +89 -0
- b1sl/contrib/django/odata_decode_middleware.py +14 -0
- b1sl/contrib/django/odata_transform_url_middleware.py +15 -0
- b1sl/py.typed +0 -0
- b1sl/saphdb/__init__.py +19 -0
- b1sl/saphdb/adapter.py +24 -0
- b1sl/saphdb/client.py +50 -0
- b1sl/saphdb/config.py +68 -0
- b1sl/saphdb/endpoints/serialnumberdetailodbc.py +100 -0
- b1sl/saphdb/exceptions/exceptions.py +19 -0
- b1sl/saphdb/main.py +18 -0
- b1sl/saphdb/models/result.py +18 -0
- b1sl/saphdb/models/serial.py +37 -0
- b1sl/saphdb/odbc_adapter.py +220 -0
- b1sl/saphdb/tests/__init__.py +0 -0
- b1sl/saphdb/tests/test_01_settings.py +37 -0
- b1sl/saphdb/tests/test_02_odbc_adapter.py +96 -0
- b1sl/saphdb/tests/test_04_serialnumberdetails_endpoint.py +131 -0
- b1sl_python-0.1.0.dist-info/METADATA +169 -0
- b1sl_python-0.1.0.dist-info/RECORD +579 -0
- b1sl_python-0.1.0.dist-info/WHEEL +4 -0
- b1sl_python-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from b1sl.b1sl.config import B1Config
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from collections import OrderedDict
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
14
|
+
from urllib.parse import parse_qs, urlencode, urlparse
|
|
15
|
+
|
|
16
|
+
from b1sl.b1sl.exceptions.exceptions import SAPConcurrencyError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ObservabilityConfig:
|
|
21
|
+
"""
|
|
22
|
+
Configuration for SDK observability features.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
hooks (dict): Dictionary mapping event names to lists of callables.
|
|
26
|
+
Supported events: 'on_response', 'on_error'.
|
|
27
|
+
context_extras (dict): Arbitrary metadata injected into every HookContext.
|
|
28
|
+
slow_request_threshold_ms (float): Threshold to trigger 'slow request' logs.
|
|
29
|
+
log_level_slow (int): Logging level for slow requests. Default is logging.WARNING.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
hooks: Dict[str, List[Callable]] = field(default_factory=dict)
|
|
33
|
+
context_extras: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
slow_request_threshold_ms: float = 5000.0
|
|
35
|
+
log_level_slow: int = logging.WARNING
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class HookContext:
|
|
40
|
+
"""
|
|
41
|
+
Immutable object passed to every hook on response or error.
|
|
42
|
+
Designed for structured logging and performance monitoring.
|
|
43
|
+
|
|
44
|
+
Privacy Note: URL components are separated to avoid logging sensitive
|
|
45
|
+
data contained in query parameters by default.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
req_id: str
|
|
49
|
+
http_method: str
|
|
50
|
+
base_url: str
|
|
51
|
+
endpoint: str
|
|
52
|
+
query_params: str # Raw query string
|
|
53
|
+
db: str
|
|
54
|
+
user: str
|
|
55
|
+
status_code: Optional[int] # None if network exception occurred
|
|
56
|
+
duration_ms: float
|
|
57
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
exc: Optional[Exception] = None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def duration_s(self) -> float:
|
|
62
|
+
"""Duration in seconds."""
|
|
63
|
+
return self.duration_ms / 1000.0
|
|
64
|
+
|
|
65
|
+
def to_log_extra(self) -> Dict[str, Any]:
|
|
66
|
+
"""Generate a flat dictionary for structured loggers."""
|
|
67
|
+
return {
|
|
68
|
+
"req_id": self.req_id,
|
|
69
|
+
"http_method": self.http_method,
|
|
70
|
+
"base_url": self.base_url,
|
|
71
|
+
"endpoint": self.endpoint,
|
|
72
|
+
"db": self.db,
|
|
73
|
+
"user": self.user,
|
|
74
|
+
"status_code": self.status_code,
|
|
75
|
+
"duration_ms": round(self.duration_ms, 3),
|
|
76
|
+
**self.extra,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class HookDispatcher:
|
|
81
|
+
"""
|
|
82
|
+
Safely manages and executes event hooks.
|
|
83
|
+
|
|
84
|
+
Hook Contract for authors:
|
|
85
|
+
- ctx is immutable; any modification attempt will raise FrozenInstanceError.
|
|
86
|
+
- Hooks are executed in order of registration (sequential).
|
|
87
|
+
- An exception in a hook DOES NOT interrupt subsequent hooks or the SDK request.
|
|
88
|
+
- Errors in hooks are caught and logged at WARNING level via exc_info.
|
|
89
|
+
- duration_ms is always available, even on network errors (status_code=None).
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, hooks: Dict[str, List[Callable]] | None = None):
|
|
93
|
+
self._hooks = hooks or {}
|
|
94
|
+
|
|
95
|
+
def dispatch(self, event: str, ctx: HookContext, logger: logging.Logger) -> None:
|
|
96
|
+
"""Synchronously dispatch a hook event."""
|
|
97
|
+
for hook in self._hooks.get(event, []):
|
|
98
|
+
try:
|
|
99
|
+
hook(ctx)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
name = getattr(hook, "__name__", "unnamed_hook")
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"Hook '{event}' (sync) {name} failed: {e}", exc_info=True
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def adispatch(
|
|
107
|
+
self, event: str, ctx: HookContext, logger: logging.Logger
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Asynchronously dispatch a hook event (supports both sync and async hooks concurrently)."""
|
|
110
|
+
async def run_hook(hook: Callable) -> None:
|
|
111
|
+
try:
|
|
112
|
+
result = hook(ctx)
|
|
113
|
+
if asyncio.iscoroutine(result):
|
|
114
|
+
await result
|
|
115
|
+
except Exception as e:
|
|
116
|
+
name = getattr(hook, "__name__", "unnamed_hook")
|
|
117
|
+
logger.warning(
|
|
118
|
+
f"Hook '{event}' (async) {name} failed: {e}", exc_info=True
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
hooks = self._hooks.get(event, [])
|
|
122
|
+
if hooks:
|
|
123
|
+
await asyncio.gather(*(run_hook(h) for h in hooks))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BaseRestAdapter:
|
|
127
|
+
"""
|
|
128
|
+
Base class for SAP B1 Service Layer HTTP adapters.
|
|
129
|
+
|
|
130
|
+
Provides shared logic for URL normalization, ETag caching,
|
|
131
|
+
and structured observability (logging + hooks).
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
config: B1Config,
|
|
137
|
+
*,
|
|
138
|
+
logger: logging.Logger | None = None,
|
|
139
|
+
version: str = "v2",
|
|
140
|
+
observability: ObservabilityConfig | None = None,
|
|
141
|
+
):
|
|
142
|
+
self._config = config
|
|
143
|
+
self.version = version
|
|
144
|
+
base_url_stripped = config.base_url.rstrip("/")
|
|
145
|
+
|
|
146
|
+
# URL Normalization logic
|
|
147
|
+
if "/b1s/" not in base_url_stripped:
|
|
148
|
+
self.raw_base_url = f"{base_url_stripped}/b1s/{version}"
|
|
149
|
+
else:
|
|
150
|
+
self.raw_base_url = base_url_stripped
|
|
151
|
+
|
|
152
|
+
# Extract attributes from config for quick access or legacy internal use
|
|
153
|
+
self._username = config.username
|
|
154
|
+
self._password = config.password
|
|
155
|
+
self._db = config.company_db
|
|
156
|
+
self._ssl_verify = config.ssl_verify
|
|
157
|
+
|
|
158
|
+
self.reuse_token = config.reuse_token
|
|
159
|
+
self.token_timeout = config.token_timeout
|
|
160
|
+
self.token_expiry: datetime | None = None
|
|
161
|
+
self.is_session_active = False
|
|
162
|
+
|
|
163
|
+
self._connect_timeout = config.connect_timeout
|
|
164
|
+
self._read_timeout = config.read_timeout
|
|
165
|
+
|
|
166
|
+
self._logger = logger or logging.getLogger(f"b1sl.{self.__class__.__name__}")
|
|
167
|
+
|
|
168
|
+
# Observability Setup
|
|
169
|
+
self._obs = observability or ObservabilityConfig()
|
|
170
|
+
self._hooks = HookDispatcher(self._obs.hooks)
|
|
171
|
+
|
|
172
|
+
self._etag_cache: OrderedDict[str, str] = OrderedDict()
|
|
173
|
+
self._etag_max_size: int = getattr(config, "etag_cache_size", 256)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_config(
|
|
177
|
+
cls,
|
|
178
|
+
config: B1Config,
|
|
179
|
+
*,
|
|
180
|
+
logger: logging.Logger | None = None,
|
|
181
|
+
version: str = "v2",
|
|
182
|
+
observability: ObservabilityConfig | None = None,
|
|
183
|
+
):
|
|
184
|
+
"""
|
|
185
|
+
Factory method to instantiate the adapter from a B1Config.
|
|
186
|
+
"""
|
|
187
|
+
return cls(
|
|
188
|
+
config=config,
|
|
189
|
+
logger=logger,
|
|
190
|
+
version=version,
|
|
191
|
+
observability=observability,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def url(self) -> str:
|
|
196
|
+
"""Returns the fully normalized Service Layer base URL (including /b1s/v1)."""
|
|
197
|
+
return self.raw_base_url
|
|
198
|
+
|
|
199
|
+
def _clear_etag(self, key: str) -> None:
|
|
200
|
+
"""Remove an ETag from the cache (e.g. after a conflict)."""
|
|
201
|
+
if key in self._etag_cache:
|
|
202
|
+
del self._etag_cache[key]
|
|
203
|
+
|
|
204
|
+
def _set_etag(self, key: str, value: str) -> None:
|
|
205
|
+
"""Update the ETag for a given resource in the LRU cache.
|
|
206
|
+
|
|
207
|
+
The key is always the canonical endpoint path (e.g.
|
|
208
|
+
``/BusinessPartners('C20000')``) so that both GET and subsequent
|
|
209
|
+
PATCH/DELETE operations share the same lookup key.
|
|
210
|
+
"""
|
|
211
|
+
if key in self._etag_cache:
|
|
212
|
+
self._etag_cache.move_to_end(key)
|
|
213
|
+
self._etag_cache[key] = value
|
|
214
|
+
if len(self._etag_cache) > self._etag_max_size:
|
|
215
|
+
self._etag_cache.popitem(last=False)
|
|
216
|
+
|
|
217
|
+
def _extract_etag(
|
|
218
|
+
self,
|
|
219
|
+
endpoint: str,
|
|
220
|
+
response_headers: dict,
|
|
221
|
+
response_body: dict | None,
|
|
222
|
+
) -> str | None:
|
|
223
|
+
"""Extract and cache the ETag for a resource after a successful request.
|
|
224
|
+
|
|
225
|
+
Extraction priority (per SAP B1 OData V4 spec):
|
|
226
|
+
1. ``ETag`` response header (preferred — avoids altering body parsing)
|
|
227
|
+
2. ``@odata.etag`` key inside the JSON body (fallback for SAP quirks)
|
|
228
|
+
|
|
229
|
+
The extracted value is automatically stored in the LRU cache.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
endpoint: The canonical resource path used as the cache key.
|
|
233
|
+
response_headers: Mapping of HTTP response headers.
|
|
234
|
+
response_body: Parsed JSON body, or ``None`` when the response has
|
|
235
|
+
no content (e.g. 204 No Content).
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The raw ETag string (including surrounding quotes and ``W/`` prefix)
|
|
239
|
+
or ``None`` when no ETag was found.
|
|
240
|
+
"""
|
|
241
|
+
etag = response_headers.get("ETag") or response_headers.get("etag")
|
|
242
|
+
if not etag and isinstance(response_body, dict):
|
|
243
|
+
etag = response_body.get("@odata.etag")
|
|
244
|
+
if etag:
|
|
245
|
+
self._set_etag(endpoint, etag)
|
|
246
|
+
return etag or None
|
|
247
|
+
|
|
248
|
+
def _build_headers(
|
|
249
|
+
self,
|
|
250
|
+
http_method: str,
|
|
251
|
+
endpoint: str,
|
|
252
|
+
extra_headers: dict | None = None,
|
|
253
|
+
) -> dict:
|
|
254
|
+
"""Build the final HTTP headers for a request, injecting ETag controls.
|
|
255
|
+
|
|
256
|
+
Injection rules:
|
|
257
|
+
- ``GET``: Adds ``If-None-Match`` if an ETag is cached (enables 304
|
|
258
|
+
Not Modified optimisation — note SAP SL may not honour this).
|
|
259
|
+
- ``PATCH`` / ``DELETE`` / ``POST`` (actions): Adds ``If-Match`` with
|
|
260
|
+
the cached ETag when one is available, enforcing optimistic concurrency.
|
|
261
|
+
If no ETag is cached the header is **omitted**, which causes SAP to
|
|
262
|
+
perform a blind override (documented SAP behaviour).
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
http_method: HTTP verb in upper-case.
|
|
266
|
+
endpoint: Canonical resource path, used to look up the ETag cache.
|
|
267
|
+
extra_headers: Optional caller-supplied headers that take precedence
|
|
268
|
+
over the defaults built here.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
A new ``dict`` ready to pass to the underlying HTTP library.
|
|
272
|
+
"""
|
|
273
|
+
headers: dict = {"Content-Type": "application/json"}
|
|
274
|
+
|
|
275
|
+
cached_etag = self._etag_cache.get(endpoint)
|
|
276
|
+
|
|
277
|
+
if http_method == "GET" and cached_etag:
|
|
278
|
+
headers["If-None-Match"] = cached_etag
|
|
279
|
+
elif http_method in {"PATCH", "DELETE", "POST"} and cached_etag:
|
|
280
|
+
# POST covers OData Actions (e.g. /BusinessPartners('C20000')/Cancel)
|
|
281
|
+
headers["If-Match"] = cached_etag
|
|
282
|
+
|
|
283
|
+
if extra_headers:
|
|
284
|
+
headers.update(extra_headers)
|
|
285
|
+
return headers
|
|
286
|
+
|
|
287
|
+
def _raise_if_concurrency_error(
|
|
288
|
+
self,
|
|
289
|
+
status_code: int,
|
|
290
|
+
sap_code: str,
|
|
291
|
+
sap_message: str,
|
|
292
|
+
endpoint: str,
|
|
293
|
+
response_body: dict | None,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Convert a SAP 412 + code -2039 response into a ``SAPConcurrencyError``.
|
|
296
|
+
|
|
297
|
+
This is the *single* place where the SDK distinguishes an ETag conflict
|
|
298
|
+
from a generic HTTP error. Both sync and async adapters call this
|
|
299
|
+
method right before raising their generic ``B1Exception``, so the
|
|
300
|
+
specialised exception is always raised first when applicable.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
status_code: HTTP status code of the failed response.
|
|
304
|
+
sap_code: SAP OData error code extracted from the response body.
|
|
305
|
+
sap_message: Human-readable SAP error message.
|
|
306
|
+
endpoint: Resource path where the conflict occurred.
|
|
307
|
+
response_body: Full parsed JSON body for the ``details`` attribute.
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
SAPConcurrencyError: Always, when status_code is 412 and
|
|
311
|
+
sap_code is ``"-2039"``.
|
|
312
|
+
"""
|
|
313
|
+
if status_code == 412 and sap_code == SAPConcurrencyError.SAP_ERROR_CODE:
|
|
314
|
+
etag_sent = self._etag_cache.get(endpoint)
|
|
315
|
+
self._clear_etag(endpoint) # Force fresh GET on next attempt
|
|
316
|
+
raise SAPConcurrencyError(
|
|
317
|
+
f"ETag conflict on '{endpoint}': {sap_message}",
|
|
318
|
+
sap_code=sap_code,
|
|
319
|
+
etag_sent=etag_sent,
|
|
320
|
+
endpoint=endpoint,
|
|
321
|
+
details=response_body,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def _redact_data(self, data: dict | None) -> dict:
|
|
325
|
+
"""Mask sensitive information in data dictionaries before logging."""
|
|
326
|
+
return {k: "***" if k == "Password" else v for k, v in (data or {}).items()}
|
|
327
|
+
|
|
328
|
+
def _generate_req_id(self) -> str:
|
|
329
|
+
"""Generate a short unique identifier for request tracing."""
|
|
330
|
+
return uuid.uuid4().hex[:16]
|
|
331
|
+
|
|
332
|
+
def _log_response(self, ctx: HookContext) -> None:
|
|
333
|
+
"""
|
|
334
|
+
Standardized logging for request outcomes.
|
|
335
|
+
Elevates level if duration exceeds threshold.
|
|
336
|
+
"""
|
|
337
|
+
level = logging.INFO
|
|
338
|
+
status_label = f" -> {ctx.status_code}" if ctx.status_code else " -> ERROR"
|
|
339
|
+
slow_label = ""
|
|
340
|
+
|
|
341
|
+
if ctx.duration_ms >= self._obs.slow_request_threshold_ms:
|
|
342
|
+
level = self._obs.log_level_slow
|
|
343
|
+
slow_label = " ⚠ SLOW"
|
|
344
|
+
|
|
345
|
+
if ctx.exc:
|
|
346
|
+
level = logging.ERROR
|
|
347
|
+
|
|
348
|
+
clean_endpoint = ctx.endpoint.lstrip("/")
|
|
349
|
+
msg = f"[{ctx.req_id}][{ctx.user}] [{ctx.http_method} /{clean_endpoint}]{status_label} ({ctx.duration_ms:.1f}ms){slow_label}"
|
|
350
|
+
self._logger.log(level, msg, extra=ctx.to_log_extra())
|
|
351
|
+
|
|
352
|
+
# ── URL Helpers ──────────────────────────────────────────────────────── #
|
|
353
|
+
|
|
354
|
+
def _clean_url(self, next_link: str) -> str:
|
|
355
|
+
"""Normalize an OData nextLink by sorting query parameters."""
|
|
356
|
+
parsed_url = urlparse(next_link)
|
|
357
|
+
query_params = parse_qs(parsed_url.query)
|
|
358
|
+
cleaned_query = urlencode(query_params, doseq=True)
|
|
359
|
+
return parsed_url._replace(query=cleaned_query).geturl()
|
|
360
|
+
|
|
361
|
+
def _get_ep_params(self, next_link: str) -> dict:
|
|
362
|
+
"""Extract query parameters from an OData nextLink into a dictionary."""
|
|
363
|
+
parsed_url = urlparse(next_link)
|
|
364
|
+
query_params = parse_qs(parsed_url.query)
|
|
365
|
+
return {k: ",".join(v) for k, v in query_params.items()}
|
|
366
|
+
|
|
367
|
+
# ── SAP OData error parsing ──────────────────────────────────────────── #
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _parse_sap_error_shared(
|
|
371
|
+
status_code: int, reason: str, body: Any
|
|
372
|
+
) -> tuple[str, str]:
|
|
373
|
+
"""Extract SAP-specific error code and message from an OData response."""
|
|
374
|
+
http_fallback = f"HTTP {status_code}: {reason}"
|
|
375
|
+
if not isinstance(body, dict):
|
|
376
|
+
return "unknown", http_fallback
|
|
377
|
+
|
|
378
|
+
error_node = body.get("error")
|
|
379
|
+
if not error_node or not isinstance(error_node, dict):
|
|
380
|
+
return "unknown", http_fallback
|
|
381
|
+
|
|
382
|
+
sap_code = str(error_node.get("code", "unknown"))
|
|
383
|
+
raw_message = error_node.get("message", "")
|
|
384
|
+
|
|
385
|
+
if isinstance(raw_message, dict):
|
|
386
|
+
sap_message = raw_message.get("value") or raw_message.get(
|
|
387
|
+
"lang", http_fallback
|
|
388
|
+
)
|
|
389
|
+
elif isinstance(raw_message, str) and raw_message:
|
|
390
|
+
sap_message = raw_message
|
|
391
|
+
else:
|
|
392
|
+
sap_message = http_fallback
|
|
393
|
+
|
|
394
|
+
return sap_code, sap_message
|
b1sl/b1sl/client.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from b1sl.b1sl.adapter_protocol import RestAdapterProtocol
|
|
6
|
+
from b1sl.b1sl.base_adapter import ObservabilityConfig
|
|
7
|
+
from b1sl.b1sl.config import B1Config
|
|
8
|
+
from b1sl.b1sl.resources._generated.client_mixin import B1ClientMixin
|
|
9
|
+
from b1sl.b1sl.rest_adapter import RestAdapter
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from b1sl.b1sl.resources.udo import UDOResource
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class B1Client(B1ClientMixin):
|
|
17
|
+
"""
|
|
18
|
+
Main synchronous entry point for the SAP B1 Service Layer SDK.
|
|
19
|
+
|
|
20
|
+
This class provides a typed interface to access SAP resources (Items,
|
|
21
|
+
Orders, etc.) and custom UDOs. It orchestrates the underlying RestAdapter
|
|
22
|
+
for session management and HTTP transport.
|
|
23
|
+
|
|
24
|
+
AI Role: Primary interface for synchronous scripts and legacy apps.
|
|
25
|
+
Use properties (e.g. client.items) to access SAP entities.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
config = B1Config.from_env()
|
|
29
|
+
client = B1Client(config)
|
|
30
|
+
item = client.items.get("A0001")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
config: B1Config,
|
|
36
|
+
logger: logging.Logger | None = None,
|
|
37
|
+
version: str = "v2",
|
|
38
|
+
adapter: RestAdapterProtocol | None = None,
|
|
39
|
+
*,
|
|
40
|
+
observability: ObservabilityConfig | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Initializes the B1Client.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
config (B1Config): Validated configuration object.
|
|
47
|
+
logger (logging.Logger, optional): Custom logger; defaults
|
|
48
|
+
to a prefixed 'b1sl.B1Client' logger.
|
|
49
|
+
version (str): Service Layer API version (v1, v2). Defaults to "v2".
|
|
50
|
+
adapter (RestAdapterProtocol, optional): Custom adapter for
|
|
51
|
+
mocking or dependency injection.
|
|
52
|
+
"""
|
|
53
|
+
self._logger = logger or logging.getLogger(f"b1sl.{self.__class__.__name__}")
|
|
54
|
+
# Private: consumers use properties from B1ClientMixin
|
|
55
|
+
self._adapter = adapter or RestAdapter(
|
|
56
|
+
config, logger=self._logger, version=version, observability=observability
|
|
57
|
+
)
|
|
58
|
+
self.version = version
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def session_id(self) -> str | None:
|
|
62
|
+
"""
|
|
63
|
+
Retrieves the current SAP session ID.
|
|
64
|
+
"""
|
|
65
|
+
return self._adapter.session_id
|
|
66
|
+
|
|
67
|
+
def connect(self) -> None:
|
|
68
|
+
"""
|
|
69
|
+
No-op for the sync client as connection is per-request,
|
|
70
|
+
provided for parity with AsyncB1Client.
|
|
71
|
+
"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def close(self) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Logs out and closes the HTTP connection pool.
|
|
77
|
+
Must be called to ensure clean shutdown if not using context manager.
|
|
78
|
+
"""
|
|
79
|
+
self._adapter.close()
|
|
80
|
+
|
|
81
|
+
def __enter__(self) -> "B1Client":
|
|
82
|
+
"""
|
|
83
|
+
Entry point for the context manager.
|
|
84
|
+
"""
|
|
85
|
+
self.connect()
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Exit point for the context manager.
|
|
91
|
+
Ensures connection pool cleanup.
|
|
92
|
+
"""
|
|
93
|
+
self.close()
|
|
94
|
+
|
|
95
|
+
def udo(self, table_name: str) -> "UDOResource":
|
|
96
|
+
"""
|
|
97
|
+
Access a User Defined Object (UDO) or User Table dynamically.
|
|
98
|
+
|
|
99
|
+
AI Role: Use this for any SAP entity not present in the pre-defined
|
|
100
|
+
service properties. Handles @U_ prefixing internally if required
|
|
101
|
+
by specific UDO implementations.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
table_name (str): The UDO table name as registered in SAP B1
|
|
105
|
+
(e.g., "CT_SDK_ASSETS").
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
UDOResource: A resource object bound to the specified UDO.
|
|
109
|
+
"""
|
|
110
|
+
from b1sl.b1sl.resources.udo import UDOResource
|
|
111
|
+
|
|
112
|
+
return UDOResource(adapter=self._adapter, table_name=table_name)
|
b1sl/b1sl/config.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
|
|
7
|
+
from b1sl.b1sl.environment import B1Env
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class B1Config:
|
|
12
|
+
"""
|
|
13
|
+
Configuration object for SAP B1 Service Layer connections.
|
|
14
|
+
|
|
15
|
+
This dataclass holds the necessary parameters to authenticate and
|
|
16
|
+
communicate with the SAP SL API. It includes support for session
|
|
17
|
+
management and timeout settings.
|
|
18
|
+
|
|
19
|
+
AI Role: Core configuration schema. Use 'from_env' or
|
|
20
|
+
'from_django_settings' for automatic mapping from the environment.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
base_url (str): Target SL URL (e.g., https://host:50000/b1s/v2).
|
|
24
|
+
username (str): B1 user name.
|
|
25
|
+
password (str): B1 user password.
|
|
26
|
+
company_db (str): Target Company Database.
|
|
27
|
+
environment (B1Env): Deployment stage. Defaults to B1Env.DEV.
|
|
28
|
+
Controls log format (JSON in PROD, human-readable otherwise).
|
|
29
|
+
Set via the ``B1SL_ENV`` environment variable.
|
|
30
|
+
ssl_verify (bool, optional): Default True.
|
|
31
|
+
reuse_token (bool, optional): Default True.
|
|
32
|
+
token_timeout (timedelta, optional): Default 900s.
|
|
33
|
+
max_page_size (int, optional): Default 20.
|
|
34
|
+
connect_timeout (float, optional): TCP timeout (seconds).
|
|
35
|
+
read_timeout (float, optional): Read timeout (seconds).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
base_url: str
|
|
39
|
+
username: str
|
|
40
|
+
password: str
|
|
41
|
+
company_db: str
|
|
42
|
+
environment: B1Env = B1Env.DEV
|
|
43
|
+
ssl_verify: bool = True
|
|
44
|
+
reuse_token: bool = True
|
|
45
|
+
token_timeout: timedelta = field(default_factory=lambda: timedelta(seconds=900))
|
|
46
|
+
max_page_size: int = 20
|
|
47
|
+
connect_timeout: float = 10.0 # seconds to establish TCP connection
|
|
48
|
+
read_timeout: float = 60.0 # seconds to wait for SAP to respond
|
|
49
|
+
etag_cache_size: int = 256
|
|
50
|
+
|
|
51
|
+
def __post_init__(self) -> None:
|
|
52
|
+
"""Validates all required parameters are present after initialisation."""
|
|
53
|
+
if not self.base_url:
|
|
54
|
+
raise ValueError("B1Config.base_url cannot be empty")
|
|
55
|
+
if not self.username or not self.password:
|
|
56
|
+
raise ValueError("B1Config credentials (username/password) cannot be empty")
|
|
57
|
+
if not self.company_db:
|
|
58
|
+
raise ValueError("B1Config.company_db cannot be empty")
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_env(cls, strict: bool = True) -> "B1Config":
|
|
62
|
+
"""
|
|
63
|
+
Factory method that loads configuration from OS environment variables.
|
|
64
|
+
|
|
65
|
+
AI Role: Standard way to load config. Expects variables prefixed
|
|
66
|
+
with B1SL_ (e.g., B1SL_BASE_URL).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
strict (bool): If True (default), raises EnvironmentError if
|
|
70
|
+
required variables are missing. If False, returns a dummy
|
|
71
|
+
config for testing/VCR playback.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
B1Config: Loaded instance.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
EnvironmentError: If strict is True and any required variable is missing.
|
|
78
|
+
"""
|
|
79
|
+
required = {
|
|
80
|
+
"base_url": "B1SL_BASE_URL",
|
|
81
|
+
"username": "B1SL_USERNAME",
|
|
82
|
+
"password": "B1SL_PASSWORD",
|
|
83
|
+
"company_db": "B1SL_COMPANY_DB",
|
|
84
|
+
}
|
|
85
|
+
missing = [key for attr, key in required.items() if not os.environ.get(key)]
|
|
86
|
+
|
|
87
|
+
if missing and strict:
|
|
88
|
+
raise EnvironmentError(
|
|
89
|
+
f"Missing required SAP B1 config env vars: {missing}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
kwargs = {
|
|
93
|
+
"base_url": os.environ.get("B1SL_BASE_URL", "https://dummy:50000/b1s/v2"),
|
|
94
|
+
"username": os.environ.get("B1SL_USERNAME", "dummy"),
|
|
95
|
+
"password": os.environ.get("B1SL_PASSWORD", "dummy"),
|
|
96
|
+
"company_db": os.environ.get("B1SL_COMPANY_DB", "SBODemoMX"),
|
|
97
|
+
"ssl_verify": os.environ.get("B1SL_SSL_VERIFY", "1") == "1",
|
|
98
|
+
"reuse_token": os.environ.get("B1SL_REUSE_TOKEN", "1") == "1",
|
|
99
|
+
"token_timeout": timedelta(
|
|
100
|
+
seconds=int(os.environ.get("B1SL_TOKEN_TIMEOUT", 900))
|
|
101
|
+
),
|
|
102
|
+
"max_page_size": int(os.environ.get("B1SL_MAX_PAGE_SIZE", 20)),
|
|
103
|
+
"etag_cache_size": int(os.environ.get("B1SL_ETAG_CACHE_SIZE", 256)),
|
|
104
|
+
"connect_timeout": float(os.environ.get("B1SL_CONNECT_TIMEOUT", 10)),
|
|
105
|
+
"read_timeout": float(os.environ.get("B1SL_READ_TIMEOUT", 60)),
|
|
106
|
+
"environment": B1Env(os.environ.get("B1SL_ENV", "dev").lower()),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return cls(**kwargs)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_django_settings(cls) -> "B1Config":
|
|
113
|
+
"""
|
|
114
|
+
Factory method that loads configuration from django.conf.settings.
|
|
115
|
+
|
|
116
|
+
AI Role: Use this only when running within a Django application.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
B1Config: Instance populated from settings.py variables.
|
|
120
|
+
"""
|
|
121
|
+
from django.conf import settings
|
|
122
|
+
|
|
123
|
+
return cls(
|
|
124
|
+
base_url=getattr(settings, "B1SL_BASE_URL", ""),
|
|
125
|
+
username=getattr(settings, "B1SL_USERNAME", ""),
|
|
126
|
+
password=getattr(settings, "B1SL_PASSWORD", ""),
|
|
127
|
+
company_db=getattr(settings, "B1SL_COMPANY_DB", ""),
|
|
128
|
+
environment=B1Env(getattr(settings, "B1SL_ENV", "dev").lower()),
|
|
129
|
+
ssl_verify=getattr(settings, "B1SL_SSL_VERIFY", True),
|
|
130
|
+
reuse_token=getattr(settings, "B1SL_REUSE_TOKEN", True),
|
|
131
|
+
token_timeout=timedelta(
|
|
132
|
+
seconds=int(getattr(settings, "B1SL_TOKEN_TIMEOUT", 900))
|
|
133
|
+
),
|
|
134
|
+
max_page_size=int(getattr(settings, "B1SL_MAX_PAGE_SIZE", 20)),
|
|
135
|
+
etag_cache_size=int(getattr(settings, "B1SL_ETAG_CACHE_SIZE", 256)),
|
|
136
|
+
connect_timeout=float(getattr(settings, "B1SL_CONNECT_TIMEOUT", 10)),
|
|
137
|
+
read_timeout=float(getattr(settings, "B1SL_READ_TIMEOUT", 60)),
|
|
138
|
+
)
|