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,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
test_03_items_resource.py — ItemsResource unit tests (pure pytest, no Django).
|
|
3
|
+
|
|
4
|
+
Renamed from test_03_items_endpoint.py which tested the deprecated endpoint
|
|
5
|
+
class and required Django. These tests mock RestAdapter so no live SAP
|
|
6
|
+
connection is needed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from unittest.mock import MagicMock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from b1sl.b1sl.exceptions.exceptions import B1Exception
|
|
14
|
+
from b1sl.b1sl.models.item import Item
|
|
15
|
+
from b1sl.b1sl.models.result import Result
|
|
16
|
+
from b1sl.b1sl.resources.items import ItemsResource
|
|
17
|
+
from b1sl.b1sl.rest_adapter import RestAdapter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_adapter():
|
|
22
|
+
adapter = MagicMock(spec=RestAdapter)
|
|
23
|
+
return adapter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def items(mock_adapter):
|
|
28
|
+
return ItemsResource(adapter=mock_adapter)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── get() ────────────────────────────────────────────────────────────────── #
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_get_returns_item(items, mock_adapter):
|
|
35
|
+
mock_adapter.get.return_value = Result(
|
|
36
|
+
status_code=200,
|
|
37
|
+
message="OK",
|
|
38
|
+
data={
|
|
39
|
+
"ItemCode": "ITEM-001",
|
|
40
|
+
"ItemName": "Widget",
|
|
41
|
+
"Valid": True,
|
|
42
|
+
"QuantityOnStock": 5.0,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
item = items.get("ITEM-001")
|
|
46
|
+
assert isinstance(item, Item)
|
|
47
|
+
assert item.item_code == "ITEM-001"
|
|
48
|
+
assert item.item_name == "Widget"
|
|
49
|
+
assert item.valid is True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_get_decodes_tyes_to_true(items, mock_adapter):
|
|
53
|
+
mock_adapter.get.return_value = Result(
|
|
54
|
+
status_code=200,
|
|
55
|
+
message="OK",
|
|
56
|
+
data={"ItemCode": "X", "Valid": "tYES"},
|
|
57
|
+
)
|
|
58
|
+
item = items.get("X")
|
|
59
|
+
assert item.valid is True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_get_decodes_tno_to_false(items, mock_adapter):
|
|
63
|
+
mock_adapter.get.return_value = Result(
|
|
64
|
+
status_code=200,
|
|
65
|
+
message="OK",
|
|
66
|
+
data={"ItemCode": "X", "Valid": "tNO"},
|
|
67
|
+
)
|
|
68
|
+
item = items.get("X")
|
|
69
|
+
assert item.valid is False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_get_with_custom_model(items, mock_adapter):
|
|
73
|
+
from pydantic import Field
|
|
74
|
+
|
|
75
|
+
from b1sl.b1sl.models.base import B1Model
|
|
76
|
+
|
|
77
|
+
class CustomItem(B1Model):
|
|
78
|
+
item_code: str = Field(alias="ItemCode")
|
|
79
|
+
custom_udf: str | None = Field(None, alias="U_Custom")
|
|
80
|
+
|
|
81
|
+
mock_adapter.get.return_value = Result(
|
|
82
|
+
status_code=200,
|
|
83
|
+
message="OK",
|
|
84
|
+
data={"ItemCode": "X", "U_Custom": "hello"},
|
|
85
|
+
)
|
|
86
|
+
result = items.get("X", model=CustomItem)
|
|
87
|
+
assert isinstance(result, CustomItem)
|
|
88
|
+
assert result.custom_udf == "hello"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ── list() ───────────────────────────────────────────────────────────────── #
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_list_returns_paginated_result(items, mock_adapter):
|
|
95
|
+
from b1sl.b1sl.models.paginated_result import PaginatedResult
|
|
96
|
+
|
|
97
|
+
mock_adapter.get.return_value = Result(
|
|
98
|
+
status_code=200,
|
|
99
|
+
message="OK",
|
|
100
|
+
data={
|
|
101
|
+
"value": [
|
|
102
|
+
{
|
|
103
|
+
"ItemCode": "A",
|
|
104
|
+
"ItemName": "Alpha",
|
|
105
|
+
"Valid": "tYES",
|
|
106
|
+
"QuantityOnStock": 1.0,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"ItemCode": "B",
|
|
110
|
+
"ItemName": "Beta",
|
|
111
|
+
"Valid": "tNO",
|
|
112
|
+
"QuantityOnStock": 0.0,
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
page = items.list()
|
|
118
|
+
assert isinstance(page, PaginatedResult)
|
|
119
|
+
rows = page.to_list()
|
|
120
|
+
assert len(rows) == 2
|
|
121
|
+
assert rows[0].item_code == "A"
|
|
122
|
+
assert isinstance(rows[0], Item) # default is now Item, not ItemPage
|
|
123
|
+
assert rows[0].valid is True
|
|
124
|
+
assert rows[1].valid is False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_list_empty_value_key(items, mock_adapter):
|
|
128
|
+
mock_adapter.get.return_value = Result(
|
|
129
|
+
status_code=200,
|
|
130
|
+
message="OK",
|
|
131
|
+
data={"value": []},
|
|
132
|
+
)
|
|
133
|
+
page = items.list()
|
|
134
|
+
assert page.to_list() == []
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ── update() returns fresh GET after 204 ─────────────────────────────────── #
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_update_re_fetches_item(items, mock_adapter):
|
|
141
|
+
from pydantic import Field
|
|
142
|
+
|
|
143
|
+
from b1sl.b1sl.models.base import B1Model
|
|
144
|
+
|
|
145
|
+
class ItemUpdate(B1Model):
|
|
146
|
+
valid: bool | None = Field(None, alias="Valid")
|
|
147
|
+
|
|
148
|
+
mock_adapter.patch.return_value = Result(
|
|
149
|
+
status_code=204, message="No Content", data=None
|
|
150
|
+
)
|
|
151
|
+
mock_adapter.get.return_value = Result(
|
|
152
|
+
status_code=200,
|
|
153
|
+
message="OK",
|
|
154
|
+
data={"ItemCode": "X", "ItemName": "Updated", "Valid": True},
|
|
155
|
+
)
|
|
156
|
+
result = items.update("X", ItemUpdate(valid=True))
|
|
157
|
+
assert result.item_name == "Updated"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_update_raises_on_unexpected_status(items, mock_adapter):
|
|
161
|
+
from pydantic import Field
|
|
162
|
+
|
|
163
|
+
from b1sl.b1sl.models.base import B1Model
|
|
164
|
+
|
|
165
|
+
class ItemUpdate(B1Model):
|
|
166
|
+
valid: bool | None = Field(None, alias="Valid")
|
|
167
|
+
|
|
168
|
+
mock_adapter.patch.return_value = Result(
|
|
169
|
+
status_code=200, message="OK", data={"ItemCode": "X"}
|
|
170
|
+
)
|
|
171
|
+
with pytest.raises(B1Exception):
|
|
172
|
+
items.update("X", ItemUpdate(valid=True))
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
test_04_serial_numbers_resource.py — SerialNumbersResource unit tests (pure pytest).
|
|
3
|
+
|
|
4
|
+
Renamed from test_04_serialnumberdetails_endpoint.py which required Django.
|
|
5
|
+
All tests use a mocked RestAdapter — no live SAP connection required.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from unittest.mock import MagicMock
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from b1sl.b1sl.exceptions.exceptions import B1Exception
|
|
13
|
+
from b1sl.b1sl.models.result import Result
|
|
14
|
+
from b1sl.b1sl.models.serialnumberdetail import SerialNumberDetails
|
|
15
|
+
from b1sl.b1sl.resources.serial_numbers import SerialNumbersResource
|
|
16
|
+
from b1sl.b1sl.rest_adapter import RestAdapter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_adapter():
|
|
21
|
+
return MagicMock(spec=RestAdapter)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def sn_resource(mock_adapter):
|
|
26
|
+
return SerialNumbersResource(adapter=mock_adapter)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── get() ────────────────────────────────────────────────────────────────── #
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_get_returns_serial_number_details(sn_resource, mock_adapter):
|
|
33
|
+
mock_adapter.get.return_value = Result(
|
|
34
|
+
status_code=200,
|
|
35
|
+
message="OK",
|
|
36
|
+
data={
|
|
37
|
+
"DocEntry": 42,
|
|
38
|
+
"ItemCode": "SN-001",
|
|
39
|
+
"MfrSerialNo": "SN123",
|
|
40
|
+
"ItemDescription": "Widget",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
result = sn_resource.get(42)
|
|
44
|
+
assert isinstance(result, SerialNumberDetails)
|
|
45
|
+
assert result.doc_entry == 42
|
|
46
|
+
assert result.item_code == "SN-001"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_get_with_custom_model(sn_resource, mock_adapter):
|
|
50
|
+
from pydantic import Field
|
|
51
|
+
|
|
52
|
+
from b1sl.b1sl.models.base import B1Model
|
|
53
|
+
|
|
54
|
+
class CustomSerial(B1Model):
|
|
55
|
+
doc_entry: int = Field(alias="DocEntry")
|
|
56
|
+
user_location: str | None = Field(None, alias="U_Ubicacion")
|
|
57
|
+
|
|
58
|
+
mock_adapter.get.return_value = Result(
|
|
59
|
+
status_code=200,
|
|
60
|
+
message="OK",
|
|
61
|
+
data={"DocEntry": 99, "U_Ubicacion": "K-11-2"},
|
|
62
|
+
)
|
|
63
|
+
result = sn_resource.get(99, model=CustomSerial)
|
|
64
|
+
assert isinstance(result, CustomSerial)
|
|
65
|
+
assert result.user_location == "K-11-2"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_get_wraps_b1exception(sn_resource, mock_adapter):
|
|
69
|
+
mock_adapter.get.side_effect = B1Exception("SAP Error -2028: Not found")
|
|
70
|
+
with pytest.raises(B1Exception, match="SerialNumbersResource.get"):
|
|
71
|
+
sn_resource.get(1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── list() ───────────────────────────────────────────────────────────────── #
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_list_returns_paginated_result(sn_resource, mock_adapter):
|
|
78
|
+
from b1sl.b1sl.models.paginated_result import PaginatedResult
|
|
79
|
+
|
|
80
|
+
mock_adapter.get.return_value = Result(
|
|
81
|
+
status_code=200,
|
|
82
|
+
message="OK",
|
|
83
|
+
data={
|
|
84
|
+
"value": [
|
|
85
|
+
{
|
|
86
|
+
"DocEntry": 1,
|
|
87
|
+
"ItemCode": "A",
|
|
88
|
+
"MfrSerialNo": "SNA",
|
|
89
|
+
"ItemDescription": "Alpha",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"DocEntry": 2,
|
|
93
|
+
"ItemCode": "B",
|
|
94
|
+
"MfrSerialNo": "SNB",
|
|
95
|
+
"ItemDescription": "Beta",
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
page = sn_resource.list()
|
|
101
|
+
assert isinstance(page, PaginatedResult)
|
|
102
|
+
rows = page.to_list()
|
|
103
|
+
assert len(rows) == 2
|
|
104
|
+
assert rows[0].doc_entry == 1
|
|
105
|
+
assert rows[1].item_code == "B"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_list_empty(sn_resource, mock_adapter):
|
|
109
|
+
mock_adapter.get.return_value = Result(
|
|
110
|
+
status_code=200,
|
|
111
|
+
message="OK",
|
|
112
|
+
data={"value": []},
|
|
113
|
+
)
|
|
114
|
+
page = sn_resource.list()
|
|
115
|
+
assert page.to_list() == []
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ── update() ─────────────────────────────────────────────────────────────── #
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_update_re_fetches_on_204(sn_resource, mock_adapter):
|
|
122
|
+
from b1sl.b1sl.models.base import B1Model
|
|
123
|
+
|
|
124
|
+
class SerialNumberUpdateTest(B1Model):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
mock_adapter.patch.return_value = Result(
|
|
128
|
+
status_code=204, message="No Content", data=None
|
|
129
|
+
)
|
|
130
|
+
mock_adapter.get.return_value = Result(
|
|
131
|
+
status_code=200,
|
|
132
|
+
message="OK",
|
|
133
|
+
data={
|
|
134
|
+
"DocEntry": 42,
|
|
135
|
+
"ItemCode": "X",
|
|
136
|
+
"MfrSerialNo": "SN-X",
|
|
137
|
+
"ItemDescription": "Widget",
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
result = sn_resource.update(42, SerialNumberUpdateTest())
|
|
141
|
+
assert result.doc_entry == 42
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_update_raises_on_unexpected_status(sn_resource, mock_adapter):
|
|
145
|
+
from b1sl.b1sl.models.base import B1Model
|
|
146
|
+
|
|
147
|
+
class SerialNumberUpdateTest(B1Model):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
mock_adapter.patch.return_value = Result(
|
|
151
|
+
status_code=200, message="OK", data={"DocEntry": 42}
|
|
152
|
+
)
|
|
153
|
+
with pytest.raises(B1Exception):
|
|
154
|
+
sn_resource.update(42, SerialNumberUpdateTest())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── PaginatedResult.to_list() caching ────────────────────────────────────── #
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_paginated_result_to_list_caches(sn_resource, mock_adapter):
|
|
161
|
+
"""to_list() must return the same list object on repeated calls."""
|
|
162
|
+
mock_adapter.get.return_value = Result(
|
|
163
|
+
status_code=200,
|
|
164
|
+
message="OK",
|
|
165
|
+
data={
|
|
166
|
+
"value": [
|
|
167
|
+
{
|
|
168
|
+
"DocEntry": 1,
|
|
169
|
+
"ItemCode": "A",
|
|
170
|
+
"MfrSerialNo": "S",
|
|
171
|
+
"ItemDescription": "D",
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
page = sn_resource.list()
|
|
177
|
+
first = page.to_list()
|
|
178
|
+
second = page.to_list()
|
|
179
|
+
assert first is second, "to_list() is not returning cached list"
|
|
180
|
+
third = list(page.data)
|
|
181
|
+
assert third == first, "data property does not replay cache"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from b1sl.b1sl.async_rest_adapter import AsyncRestAdapter
|
|
7
|
+
from b1sl.b1sl.base_adapter import HookContext, HookDispatcher, ObservabilityConfig
|
|
8
|
+
from b1sl.b1sl.config import B1Config
|
|
9
|
+
from b1sl.b1sl.rest_adapter import RestAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_hook_context_immutability():
|
|
13
|
+
ctx = HookContext(
|
|
14
|
+
req_id="123",
|
|
15
|
+
http_method="GET",
|
|
16
|
+
base_url="http://test",
|
|
17
|
+
endpoint="/Items",
|
|
18
|
+
query_params="",
|
|
19
|
+
db="db",
|
|
20
|
+
user="user",
|
|
21
|
+
status_code=200,
|
|
22
|
+
duration_ms=10.0,
|
|
23
|
+
)
|
|
24
|
+
with pytest.raises(AttributeError): # frozen=True
|
|
25
|
+
ctx.status_code = 500
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_to_log_extra_contract():
|
|
29
|
+
extra_data = {"test_key": "test_val"}
|
|
30
|
+
ctx = HookContext(
|
|
31
|
+
req_id="123",
|
|
32
|
+
http_method="GET",
|
|
33
|
+
base_url="http://test",
|
|
34
|
+
endpoint="/Items",
|
|
35
|
+
query_params="",
|
|
36
|
+
db="db",
|
|
37
|
+
user="user",
|
|
38
|
+
status_code=200,
|
|
39
|
+
duration_ms=10.5,
|
|
40
|
+
extra=extra_data,
|
|
41
|
+
)
|
|
42
|
+
log_extra = ctx.to_log_extra()
|
|
43
|
+
|
|
44
|
+
assert log_extra["req_id"] == "123"
|
|
45
|
+
assert log_extra["duration_ms"] == 10.5
|
|
46
|
+
assert log_extra["test_key"] == "test_val"
|
|
47
|
+
assert (
|
|
48
|
+
"query_params" not in log_extra
|
|
49
|
+
) # Should not be in log_extra by default for privacy
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_hook_dispatcher_isolation():
|
|
53
|
+
"""Verify that a failing hook does not stop other hooks or the dispatcher."""
|
|
54
|
+
logger = MagicMock(spec=logging.Logger)
|
|
55
|
+
|
|
56
|
+
def failing_hook(ctx):
|
|
57
|
+
raise ValueError("Boom!")
|
|
58
|
+
|
|
59
|
+
success_mock = MagicMock()
|
|
60
|
+
|
|
61
|
+
dispatcher = HookDispatcher(hooks={"on_response": [failing_hook, success_mock]})
|
|
62
|
+
ctx = MagicMock(spec=HookContext)
|
|
63
|
+
|
|
64
|
+
# Should not raise exception
|
|
65
|
+
dispatcher.dispatch("on_response", ctx, logger)
|
|
66
|
+
|
|
67
|
+
# Second hook should have been called
|
|
68
|
+
success_mock.assert_called_once_with(ctx)
|
|
69
|
+
# Logger should have warned about the failure
|
|
70
|
+
assert logger.warning.called
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_async_hook_dispatcher_isolation():
|
|
75
|
+
logger = MagicMock(spec=logging.Logger)
|
|
76
|
+
|
|
77
|
+
async def failing_async_hook(ctx):
|
|
78
|
+
raise ValueError("Async Boom!")
|
|
79
|
+
|
|
80
|
+
success_async_mock = AsyncMock()
|
|
81
|
+
|
|
82
|
+
dispatcher = HookDispatcher(
|
|
83
|
+
hooks={"on_response": [failing_async_hook, success_async_mock]}
|
|
84
|
+
)
|
|
85
|
+
ctx = MagicMock(spec=HookContext)
|
|
86
|
+
|
|
87
|
+
await dispatcher.adispatch("on_response", ctx, logger)
|
|
88
|
+
|
|
89
|
+
success_async_mock.assert_called_once_with(ctx)
|
|
90
|
+
assert logger.warning.called
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_rest_adapter_observability_integration():
|
|
94
|
+
"""Integration test for RestAdapter with ObservabilityConfig."""
|
|
95
|
+
hook_mock = MagicMock()
|
|
96
|
+
obs = ObservabilityConfig(
|
|
97
|
+
hooks={"on_response": [hook_mock]}, context_extras={"tenant": "admin"}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
config = B1Config("http://sap", "user", "pass", "db")
|
|
101
|
+
adapter = RestAdapter(config, observability=obs)
|
|
102
|
+
|
|
103
|
+
# Mock transport
|
|
104
|
+
adapter._execute_request = MagicMock()
|
|
105
|
+
mock_resp = MagicMock()
|
|
106
|
+
mock_resp.status_code = 200
|
|
107
|
+
mock_resp.reason = "OK"
|
|
108
|
+
mock_resp.content = b"{}"
|
|
109
|
+
mock_resp.json.return_value = {}
|
|
110
|
+
mock_resp.headers = {}
|
|
111
|
+
adapter._execute_request.return_value = mock_resp
|
|
112
|
+
|
|
113
|
+
adapter._do("GET", "Items", ep_params={"$top": 1})
|
|
114
|
+
|
|
115
|
+
# Verify hook was called
|
|
116
|
+
assert hook_mock.called
|
|
117
|
+
ctx = hook_mock.call_args[0][0]
|
|
118
|
+
assert isinstance(ctx, HookContext)
|
|
119
|
+
assert ctx.endpoint == "/Items"
|
|
120
|
+
assert ctx.query_params == "%24top=1"
|
|
121
|
+
assert ctx.extra["tenant"] == "admin"
|
|
122
|
+
assert ctx.duration_ms > 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pytest.mark.asyncio
|
|
126
|
+
async def test_async_rest_adapter_observability_integration():
|
|
127
|
+
hook_mock = MagicMock()
|
|
128
|
+
obs = ObservabilityConfig(hooks={"on_response": [hook_mock]})
|
|
129
|
+
|
|
130
|
+
config = B1Config("http://sap", "user", "pass", "db")
|
|
131
|
+
adapter = AsyncRestAdapter(config, observability=obs)
|
|
132
|
+
|
|
133
|
+
# Mock httpx client
|
|
134
|
+
adapter._client = AsyncMock()
|
|
135
|
+
mock_resp = MagicMock()
|
|
136
|
+
mock_resp.status_code = 200
|
|
137
|
+
mock_resp.reason_phrase = "OK"
|
|
138
|
+
mock_resp.content = b"{}"
|
|
139
|
+
mock_resp.json.return_value = {}
|
|
140
|
+
mock_resp.headers = {}
|
|
141
|
+
adapter._client.request.return_value = mock_resp
|
|
142
|
+
|
|
143
|
+
from datetime import datetime, timedelta
|
|
144
|
+
|
|
145
|
+
adapter.is_session_active = True
|
|
146
|
+
adapter.token_expiry = datetime.now() + timedelta(hours=1)
|
|
147
|
+
|
|
148
|
+
await adapter._do("GET", "Items")
|
|
149
|
+
|
|
150
|
+
assert hook_mock.called
|
|
151
|
+
ctx = hook_mock.call_args[0][0]
|
|
152
|
+
assert ctx.endpoint == "/Items"
|
|
153
|
+
assert ctx.duration_ms > 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_test_logger(name="test_logger", level=logging.DEBUG):
|
|
5
|
+
logger = logging.getLogger(name)
|
|
6
|
+
logger.setLevel(level)
|
|
7
|
+
|
|
8
|
+
if not logger.handlers:
|
|
9
|
+
# Create console handler and set level to debug
|
|
10
|
+
ch = logging.StreamHandler()
|
|
11
|
+
ch.setLevel(level)
|
|
12
|
+
|
|
13
|
+
# Create formatter
|
|
14
|
+
formatter = logging.Formatter(
|
|
15
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Add formatter to ch
|
|
19
|
+
ch.setFormatter(formatter)
|
|
20
|
+
|
|
21
|
+
# Add ch to logger
|
|
22
|
+
logger.addHandler(ch)
|
|
23
|
+
|
|
24
|
+
return logger
|
b1sl/contrib/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
b1sl.contrib.django
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Django-specific contributions for SAP B1 integration, including middleware.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import SapMiddlewareBase
|
|
8
|
+
from .odata_decode_middleware import ODataDecodeMiddleware
|
|
9
|
+
from .odata_transform_url_middleware import ODataTransformURLMiddleware
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SapMiddlewareBase",
|
|
13
|
+
"ODataDecodeMiddleware",
|
|
14
|
+
"ODataTransformURLMiddleware",
|
|
15
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
b1sl.contrib.django.base
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Framework-agnostic base class for all SAP-aware Django middleware.
|
|
5
|
+
Supports both WSGI (__call__) and ASGI (__acall__) deployments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Callable
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SapMiddlewareBase:
|
|
17
|
+
"""
|
|
18
|
+
Drop-in Django middleware base that works with both:
|
|
19
|
+
- WSGI: Django calls __call__(request) synchronously.
|
|
20
|
+
- ASGI: Django calls __call__(scope, receive, send) or uses
|
|
21
|
+
the middleware with channels / uvicorn via __acall__.
|
|
22
|
+
|
|
23
|
+
Subclass and override `process_request` / `process_response`
|
|
24
|
+
for custom logic.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
sync_capable = True
|
|
28
|
+
async_capable = True
|
|
29
|
+
|
|
30
|
+
def __init__(self, get_response: Callable) -> None:
|
|
31
|
+
self.get_response = get_response
|
|
32
|
+
# Detect if the downstream handler is async
|
|
33
|
+
import asyncio
|
|
34
|
+
|
|
35
|
+
self._is_coroutine = asyncio.iscoroutinefunction(self.get_response)
|
|
36
|
+
|
|
37
|
+
# ------------------------------------------------------------------ #
|
|
38
|
+
# Hooks — override in subclasses #
|
|
39
|
+
# ------------------------------------------------------------------ #
|
|
40
|
+
|
|
41
|
+
def process_request(self, request):
|
|
42
|
+
"""Synchronous pre-processing hook. Return None to continue."""
|
|
43
|
+
|
|
44
|
+
def process_response(self, request, response):
|
|
45
|
+
"""Synchronous post-processing hook. Must return response."""
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
async def async_process_request(self, request):
|
|
49
|
+
"""Async pre-processing hook. Return None to continue."""
|
|
50
|
+
|
|
51
|
+
async def async_process_response(self, request, response):
|
|
52
|
+
"""Async post-processing hook. Must return response."""
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
# ------------------------------------------------------------------ #
|
|
56
|
+
# WSGI path #
|
|
57
|
+
# ------------------------------------------------------------------ #
|
|
58
|
+
|
|
59
|
+
def __call__(self, request):
|
|
60
|
+
if self._is_coroutine:
|
|
61
|
+
# Do not call this synchronously on an async handler —
|
|
62
|
+
# Django will always use __acall__ in that case.
|
|
63
|
+
import asyncio
|
|
64
|
+
|
|
65
|
+
return asyncio.get_event_loop().run_until_complete(self.__acall__(request))
|
|
66
|
+
|
|
67
|
+
short_circuit = self.process_request(request)
|
|
68
|
+
if short_circuit is not None:
|
|
69
|
+
return short_circuit
|
|
70
|
+
|
|
71
|
+
response = self.get_response(request)
|
|
72
|
+
|
|
73
|
+
return self.process_response(request, response)
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------ #
|
|
76
|
+
# ASGI path #
|
|
77
|
+
# ------------------------------------------------------------------ #
|
|
78
|
+
|
|
79
|
+
async def __acall__(self, request):
|
|
80
|
+
short_circuit = await self.async_process_request(request)
|
|
81
|
+
if short_circuit is not None:
|
|
82
|
+
return short_circuit
|
|
83
|
+
|
|
84
|
+
if self._is_coroutine:
|
|
85
|
+
response = await self.get_response(request)
|
|
86
|
+
else:
|
|
87
|
+
response = self.get_response(request)
|
|
88
|
+
|
|
89
|
+
return await self.async_process_response(request, response)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from urllib.parse import unquote
|
|
2
|
+
|
|
3
|
+
from .base import SapMiddlewareBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ODataDecodeMiddleware(SapMiddlewareBase):
|
|
7
|
+
"""URL-decodes percent-encoded OData query strings before Django routing."""
|
|
8
|
+
|
|
9
|
+
def process_request(self, request):
|
|
10
|
+
if qs := request.META.get("QUERY_STRING"):
|
|
11
|
+
request.META["QUERY_STRING"] = unquote(qs)
|
|
12
|
+
|
|
13
|
+
async def async_process_request(self, request):
|
|
14
|
+
self.process_request(request) # pure string op, no I/O
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import SapMiddlewareBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ODataTransformURLMiddleware(SapMiddlewareBase):
|
|
5
|
+
"""Transforms OData-style URLs (replacing parenthesis) before Django routing."""
|
|
6
|
+
|
|
7
|
+
def process_request(self, request):
|
|
8
|
+
path = request.path
|
|
9
|
+
if "(" in path and ")" in path:
|
|
10
|
+
# Reemplazar '(' y ')' con '/'
|
|
11
|
+
request.path = path.replace("(", "/").replace(")", "")
|
|
12
|
+
print(request.path)
|
|
13
|
+
|
|
14
|
+
async def async_process_request(self, request):
|
|
15
|
+
self.process_request(request) # pure string op, no I/O
|
b1sl/py.typed
ADDED
|
File without changes
|
b1sl/saphdb/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SAP HANA Database client SDK — public interface.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from b1sl.saphdb.client import SapHDBClient
|
|
6
|
+
from b1sl.saphdb.config import SapHDBConfig
|
|
7
|
+
from b1sl.saphdb.exceptions.exceptions import (
|
|
8
|
+
SapHAClientConnectionError,
|
|
9
|
+
SapHAClientException,
|
|
10
|
+
SapHAClientResponseError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SapHDBClient",
|
|
15
|
+
"SapHDBConfig",
|
|
16
|
+
"SapHAClientException",
|
|
17
|
+
"SapHAClientConnectionError",
|
|
18
|
+
"SapHAClientResponseError",
|
|
19
|
+
]
|