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,347 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from urllib.parse import urlencode
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from b1sl.b1sl.base_adapter import BaseRestAdapter, HookContext, ObservabilityConfig
|
|
13
|
+
from b1sl.b1sl.config import B1Config
|
|
14
|
+
from b1sl.b1sl.exceptions.exceptions import (
|
|
15
|
+
B1AuthError,
|
|
16
|
+
B1Exception,
|
|
17
|
+
B1NotFoundError,
|
|
18
|
+
B1ValidationError,
|
|
19
|
+
SAPConcurrencyError,
|
|
20
|
+
)
|
|
21
|
+
from b1sl.b1sl.models.result import Result
|
|
22
|
+
|
|
23
|
+
_HTTP_STATUS_TO_EXC: dict[int, type] = {
|
|
24
|
+
400: B1ValidationError,
|
|
25
|
+
401: B1AuthError,
|
|
26
|
+
404: B1NotFoundError,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AsyncRestAdapter(BaseRestAdapter):
|
|
31
|
+
"""
|
|
32
|
+
Asynchronous HTTP adapter for SAP B1 Service Layer using httpx.
|
|
33
|
+
|
|
34
|
+
This adapter provides high-concurrency support with thread-safe (async-safe)
|
|
35
|
+
session management via an internal asyncio.Lock. It handles automatic
|
|
36
|
+
re-authentication and structured logging.
|
|
37
|
+
|
|
38
|
+
AI Role: Recommended for FastAPI, Temporal, or bulk async tasks.
|
|
39
|
+
Prevents race conditions when multiple concurrent tasks hit the session.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def __init__(self, *args, session_id: str | None = None, **kwargs):
|
|
45
|
+
"""
|
|
46
|
+
Initializes the async httpx client and session lock.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
session_id (str, optional): An existing B1SESSION cookie to reuse.
|
|
50
|
+
Prevents a full login if already authenticated.
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(*args, **kwargs)
|
|
53
|
+
self._initial_session_id = session_id
|
|
54
|
+
self._client: httpx.AsyncClient | None = None
|
|
55
|
+
self._lock: asyncio.Lock | None = None
|
|
56
|
+
|
|
57
|
+
async def _get_lock(self) -> asyncio.Lock:
|
|
58
|
+
if self._lock is None:
|
|
59
|
+
self._lock = asyncio.Lock()
|
|
60
|
+
return self._lock
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def session_id(self) -> str | None:
|
|
64
|
+
"""
|
|
65
|
+
Retrieves the current SAP session ID from the httpx cookie jar.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: The B1SESSION cookie value or None.
|
|
69
|
+
"""
|
|
70
|
+
if not self._client:
|
|
71
|
+
return None
|
|
72
|
+
return self._client.cookies.get("B1SESSION")
|
|
73
|
+
|
|
74
|
+
async def connect(self) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Initializes the underlying HTTP client and logs in.
|
|
77
|
+
Must be called if not using the async context manager.
|
|
78
|
+
"""
|
|
79
|
+
if not self._client:
|
|
80
|
+
self._client = httpx.AsyncClient(
|
|
81
|
+
verify=self._ssl_verify,
|
|
82
|
+
timeout=httpx.Timeout(
|
|
83
|
+
self._connect_timeout,
|
|
84
|
+
read=self._read_timeout,
|
|
85
|
+
write=self._read_timeout,
|
|
86
|
+
pool=self._connect_timeout,
|
|
87
|
+
),
|
|
88
|
+
follow_redirects=True,
|
|
89
|
+
)
|
|
90
|
+
# Hydrate session if provided
|
|
91
|
+
if self._initial_session_id:
|
|
92
|
+
self._client.cookies.set("B1SESSION", self._initial_session_id)
|
|
93
|
+
self.is_session_active = True
|
|
94
|
+
# We don't have an expiry date, so we set it to None, which
|
|
95
|
+
# forces the 401-retry logic to handle it if it's already expired.
|
|
96
|
+
self.token_expiry = None
|
|
97
|
+
self._is_closed = False
|
|
98
|
+
await self.ensure_session()
|
|
99
|
+
|
|
100
|
+
async def aclose(self) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Logs out and closes the underlying HTTP client pool.
|
|
103
|
+
Must be called to ensure clean shutdown if not using the context manager.
|
|
104
|
+
"""
|
|
105
|
+
if getattr(self, "_is_closed", False):
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
if self.is_session_active:
|
|
109
|
+
try:
|
|
110
|
+
await self.logout()
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self._logger.warning(
|
|
113
|
+
f"[{self._username}] Failed to logout during cleanup: {e}"
|
|
114
|
+
)
|
|
115
|
+
if self._client:
|
|
116
|
+
await self._client.aclose()
|
|
117
|
+
self._client = None
|
|
118
|
+
|
|
119
|
+
self._is_closed = True
|
|
120
|
+
|
|
121
|
+
async def __aenter__(self) -> AsyncRestAdapter:
|
|
122
|
+
"""
|
|
123
|
+
Async context manager entry point. Initialises the HTTP client.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
AsyncRestAdapter: The initialised instance.
|
|
127
|
+
"""
|
|
128
|
+
await self.connect()
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Async context manager exit point. Performs logout and closes the client.
|
|
134
|
+
"""
|
|
135
|
+
await self.aclose()
|
|
136
|
+
|
|
137
|
+
async def ensure_session(self, force_refresh_if_expiry: Optional[datetime] = None) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Ensures a valid SAP session exists before a request.
|
|
140
|
+
|
|
141
|
+
Uses an internal asyncio.Lock to prevent multiple concurrent login
|
|
142
|
+
attempts when many tasks start simultaneously. If force_refresh_if_expiry
|
|
143
|
+
is provided, it will forcefully invalidate the session ONLY if the token
|
|
144
|
+
hasn't been refreshed by another task in the meantime.
|
|
145
|
+
"""
|
|
146
|
+
if not self._client:
|
|
147
|
+
raise B1Exception("AsyncRestAdapter not initialized. Call connect() first.")
|
|
148
|
+
lock = await self._get_lock()
|
|
149
|
+
async with lock:
|
|
150
|
+
if force_refresh_if_expiry is not None and self.token_expiry == force_refresh_if_expiry:
|
|
151
|
+
self.is_session_active = False
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
not self.is_session_active
|
|
155
|
+
or not self.token_expiry
|
|
156
|
+
or datetime.now() >= self.token_expiry
|
|
157
|
+
):
|
|
158
|
+
await self.login()
|
|
159
|
+
|
|
160
|
+
async def login(self) -> Result:
|
|
161
|
+
"""
|
|
162
|
+
Asynchronously authenticates with the SAP B1 Service Layer.
|
|
163
|
+
"""
|
|
164
|
+
data = {
|
|
165
|
+
"UserName": self._username,
|
|
166
|
+
"Password": self._password,
|
|
167
|
+
"CompanyDB": self._db,
|
|
168
|
+
}
|
|
169
|
+
self._logger.info(f"Logging in to SAP B1 at {self.raw_base_url}...")
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# We call _do which handles its own timing and logging
|
|
173
|
+
return await self._do(
|
|
174
|
+
http_method="POST", endpoint="Login", data=data, _is_login=True
|
|
175
|
+
)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self._logger.error(f"Login failed: {e}")
|
|
178
|
+
raise B1AuthError(f"Login failed: {e}") from e
|
|
179
|
+
|
|
180
|
+
async def logout(self) -> Result:
|
|
181
|
+
"""
|
|
182
|
+
Asynchronously releases the SAP B1 session license.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Result: Current logout state.
|
|
186
|
+
"""
|
|
187
|
+
if not self._client:
|
|
188
|
+
return Result(status_code=200, message="Closed")
|
|
189
|
+
try:
|
|
190
|
+
response = await self._client.post(f"{self.raw_base_url}/Logout")
|
|
191
|
+
self.is_session_active = False
|
|
192
|
+
self.token_expiry = None
|
|
193
|
+
return Result(
|
|
194
|
+
status_code=response.status_code, message=response.reason_phrase
|
|
195
|
+
)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
self._logger.warning(f"[{self._username}] Logout failed: {e}")
|
|
198
|
+
return Result(status_code=500, message=str(e))
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def _parse_sap_error(response: httpx.Response) -> tuple[str, str]:
|
|
202
|
+
"""Parses error information from an Httpx response object."""
|
|
203
|
+
try:
|
|
204
|
+
body = response.json()
|
|
205
|
+
except Exception:
|
|
206
|
+
body = None
|
|
207
|
+
return AsyncRestAdapter._parse_sap_error_shared(
|
|
208
|
+
response.status_code, response.reason_phrase, body
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
async def _do(
|
|
212
|
+
self,
|
|
213
|
+
http_method: str,
|
|
214
|
+
endpoint: str,
|
|
215
|
+
ep_params=None,
|
|
216
|
+
data=None,
|
|
217
|
+
_is_login: bool = False,
|
|
218
|
+
_retry_once=True,
|
|
219
|
+
) -> Result:
|
|
220
|
+
"""
|
|
221
|
+
Dispatches an asynchronous HTTP request to SAP SL.
|
|
222
|
+
Implements Senior Observability (Timing + Structured Logging + Async Hooks).
|
|
223
|
+
"""
|
|
224
|
+
# Removed redundant imports moved to module level
|
|
225
|
+
if not _is_login:
|
|
226
|
+
await self.ensure_session()
|
|
227
|
+
|
|
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
|
+
log_data = self._redact_data(data)
|
|
233
|
+
self._logger.debug(f"[{req_id}] data={log_data}")
|
|
234
|
+
|
|
235
|
+
start_time = time.perf_counter()
|
|
236
|
+
exc_captured: Exception | None = None
|
|
237
|
+
response: httpx.Response | None = None
|
|
238
|
+
is_success = False
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
# ── ETag: inject If-None-Match (GET) or If-Match (PATCH/DELETE/POST) ──
|
|
242
|
+
headers = self._build_headers(http_method, endpoint_path)
|
|
243
|
+
response = await self._client.request(
|
|
244
|
+
method=http_method, url=full_url, params=ep_params, json=data,
|
|
245
|
+
headers=headers,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if response.status_code == 401 and _retry_once and not _is_login:
|
|
249
|
+
self._logger.warning(f"[{req_id}] 401 Unauthorized - retrying login...")
|
|
250
|
+
await self.ensure_session(force_refresh_if_expiry=self.token_expiry)
|
|
251
|
+
# Recursive call will handle its own finally block,
|
|
252
|
+
# but we need to return here to avoid double-logging/hooking.
|
|
253
|
+
return await self._do(
|
|
254
|
+
http_method, endpoint, ep_params, data, _is_login, _retry_once=False
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
response.raise_for_status()
|
|
258
|
+
is_success = True
|
|
259
|
+
except httpx.HTTPStatusError as e:
|
|
260
|
+
exc_captured = e
|
|
261
|
+
sap_code, sap_msg = self._parse_sap_error(e.response)
|
|
262
|
+
# ── Raise specialised exception before falling back to B1Exception ──
|
|
263
|
+
try:
|
|
264
|
+
body = e.response.json() if e.response.content else None
|
|
265
|
+
except Exception:
|
|
266
|
+
body = None
|
|
267
|
+
self._raise_if_concurrency_error(
|
|
268
|
+
e.response.status_code, sap_code, sap_msg, endpoint_path, body
|
|
269
|
+
)
|
|
270
|
+
raise B1Exception(f"SAP Error {sap_code}: {sap_msg}") from e
|
|
271
|
+
except Exception as e:
|
|
272
|
+
exc_captured = e
|
|
273
|
+
raise B1Exception(f"Request failed: {e}") from e
|
|
274
|
+
finally:
|
|
275
|
+
# Only log/hook if this is not the first attempt of a 401 retry
|
|
276
|
+
if not (
|
|
277
|
+
response is not None
|
|
278
|
+
and response.status_code == 401
|
|
279
|
+
and _retry_once
|
|
280
|
+
and not _is_login
|
|
281
|
+
):
|
|
282
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
283
|
+
status_code = response.status_code if response is not None else None
|
|
284
|
+
|
|
285
|
+
ctx = HookContext(
|
|
286
|
+
req_id=req_id,
|
|
287
|
+
http_method=http_method,
|
|
288
|
+
base_url=self.raw_base_url,
|
|
289
|
+
endpoint=endpoint_path,
|
|
290
|
+
query_params=urlencode(ep_params) if ep_params else "",
|
|
291
|
+
db=self._db,
|
|
292
|
+
user=self._username,
|
|
293
|
+
status_code=status_code,
|
|
294
|
+
duration_ms=duration_ms,
|
|
295
|
+
extra=dict(self._obs.context_extras),
|
|
296
|
+
exc=exc_captured,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
self._log_response(ctx)
|
|
300
|
+
await self._hooks.adispatch(
|
|
301
|
+
"on_error" if exc_captured else "on_response", ctx, self._logger
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if _is_login and is_success and response is not None:
|
|
305
|
+
data_out = response.json()
|
|
306
|
+
timeout_min = data_out.get("SessionTimeout", 30)
|
|
307
|
+
self.token_expiry = datetime.now() + timedelta(minutes=timeout_min - 2)
|
|
308
|
+
self.is_session_active = True
|
|
309
|
+
|
|
310
|
+
if is_success and response is not None:
|
|
311
|
+
data_out = response.json() if response.content else None
|
|
312
|
+
# ── ETag: extract from header (preferred) or body fallback ──
|
|
313
|
+
self._extract_etag(endpoint_path, dict(response.headers), data_out)
|
|
314
|
+
return Result(
|
|
315
|
+
status_code=response.status_code,
|
|
316
|
+
message=response.reason_phrase,
|
|
317
|
+
data=data_out,
|
|
318
|
+
next_link=data_out.get("odata.nextLink")
|
|
319
|
+
if isinstance(data_out, dict)
|
|
320
|
+
else None,
|
|
321
|
+
next_params=self._get_ep_params(data_out.get("odata.nextLink"))
|
|
322
|
+
if isinstance(data_out, dict) and data_out.get("odata.nextLink")
|
|
323
|
+
else None,
|
|
324
|
+
metadata=data_out.get("odata.metadata")
|
|
325
|
+
if isinstance(data_out, dict)
|
|
326
|
+
else None,
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
raise B1Exception(
|
|
330
|
+
f"HTTP Error {response.status_code if response else 'Unknown'}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
async def get(self, endpoint, ep_params=None, data=None):
|
|
334
|
+
"""Execute an asynchronous GET request."""
|
|
335
|
+
return await self._do("GET", endpoint, ep_params, data)
|
|
336
|
+
|
|
337
|
+
async def post(self, endpoint, ep_params=None, data=None):
|
|
338
|
+
"""Execute an asynchronous POST request."""
|
|
339
|
+
return await self._do("POST", endpoint, ep_params, data)
|
|
340
|
+
|
|
341
|
+
async def patch(self, endpoint, ep_params=None, data=None):
|
|
342
|
+
"""Execute an asynchronous PATCH request."""
|
|
343
|
+
return await self._do("PATCH", endpoint, ep_params, data)
|
|
344
|
+
|
|
345
|
+
async def delete(self, endpoint, ep_params=None, data=None):
|
|
346
|
+
"""Execute an asynchronous DELETE request."""
|
|
347
|
+
return await self._do("DELETE", endpoint, ep_params, data)
|