diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 0000000..1748b38
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,79 @@
+name: 🐞 Bug
+description: Report a bug or an issue you've found
+title: "[Bug]
"
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: checkboxes
+ attributes:
+ label: Is this a new bug?
+ description: >
+ In other words: Is this an error, flaw, failure or fault? Please search issues to see if someone has already reported the bug you encountered.
+ options:
+ - label: I believe this is a new bug
+ required: true
+ - label: I have searched the existing issues, and I could not find an existing issue for this bug
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: A concise description of what you're experiencing.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A concise description of what you expected to happen.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ placeholder: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **OS**: Ubuntu 20.04
+ - **Language version**: Python 3.10.11 (`python --version`)
+ - **SDK Name**: PayPal (If you are using this library as a dependency, please name the parent SDK)
+ value: |
+ - **OS**:
+ - **Language version**:
+ - **SDK Name**:
+ render: markdown
+ validations:
+ required: true
+
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ If applicable, log output to help explain your problem.
+ render: shell
+ validations:
+ required: false
+
+ - type: textarea
+ attributes:
+ label: Additional Context
+ description: |
+ Links? References? Anything that will give us more context about the issue you are encountering!
+ validations:
+ required: false
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 22a70ea..0000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: bug, reported-by-consumer
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. (step 1)
-2. (step 2)
-3. (step 3)
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Environment**
-For example: Windows/Linux
-
-**Library version**
-For example: 0.1.0
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 0000000..c5dd8ce
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,54 @@
+name: ✨ Feature
+description: Propose an extension
+title: "[Feature] "
+labels: ["enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this feature request!
+ - type: checkboxes
+ attributes:
+ label: Is this your first time submitting a feature request?
+ description: >
+ We want to make sure that features are distinct and discoverable,
+ so that other members of the community can find them and offer their thoughts.
+
+ Issues are the right place to request extensions of existing functionality.
+ options:
+ - label: I have searched the existing issues, and I could not find an existing issue for this feature
+ required: true
+ - label: I am requesting an extension of the existing functionality
+ - type: textarea
+ attributes:
+ label: Describe the feature
+ description: A clear and concise description of what you want to happen.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe alternatives you've considered
+ description: |
+ A clear and concise description of any alternative solutions or features you've considered.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Who will this benefit?
+ description: |
+ What kind of use case will this feature be useful for? Please be specific and provide examples, this will help us prioritize properly.
+ validations:
+ required: false
+ - type: input
+ attributes:
+ label: Are you interested in contributing this feature?
+ description: Let us know if you want to write some code, and how we can help.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Anything else?
+ description: |
+ Links? References? Anything that will give us more context about the feature you are suggesting!
+ validations:
+ required: false
\ No newline at end of file
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index 62c2164..06313d4 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest]
+ os: [ubuntu-22.04]
python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml
index 465fbb4..048a1bb 100644
--- a/.github/workflows/test-runner.yml
+++ b/.github/workflows/test-runner.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest]
+ os: [ubuntu-22.04]
python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..756f41d
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,60 @@
+# Contributing to apimatic/core-lib-python
+
+Thank you for your interest in contributing! 🎉 Your contributions help make this project better. We value open-source contributions to this library, please take a few minutes to review this guide before you start.
+
+---
+
+## 💡 How to Contribute
+
+### 📌 1. Reporting Issues
+
+- Search existing issues before opening a new one.
+- Use a **descriptive title** and provide clear steps to reproduce.
+- Include relevant logs, screenshots, or error messages.
+
+### 🔧 2. Making Changes
+
+#### Step 1: Create a Branch
+
+- Create a new branch from `main`:
+ ```sh
+ git checkout -b your-feature-name
+ ```
+
+#### Step 2: Make Your Changes
+
+- Follow the project's **coding standards**.
+- Add **unit tests** if applicable.
+- Ensure your changes **do not break existing functionality**.
+
+#### Step 3: Commit Changes
+
+- Use clear and descriptive commit messages:
+ ```sh
+ git commit -m "feat: Add feature description"
+ ```
+
+#### Step 4: Push & Open a PR
+
+- Push your branch:
+ ```sh
+ git push origin your-feature-name
+ ```
+- Open a **Pull Request (PR)** on GitHub:
+ - Provide a clear **description** of the changes.
+ - Mention related **issue numbers**.
+ - Request a **review** from maintainers.
+ - Make sure your changes clear all the **PR Checks**.
+
+---
+
+## 📜 License
+
+By contributing, you agree that your contributions will be licensed under the project's [LICENSE](LICENSE).
+
+---
+
+💬 **Questions?**
+If you need help, feel free to open issues, we will be happy to help.
+
+Happy Coding! 🚀
diff --git a/apimatic_core/configurations/global_configuration.py b/apimatic_core/configurations/global_configuration.py
index 2befe69..f726b83 100644
--- a/apimatic_core/configurations/global_configuration.py
+++ b/apimatic_core/configurations/global_configuration.py
@@ -1,3 +1,5 @@
+from requests.structures import CaseInsensitiveDict
+
from apimatic_core.http.configurations.http_client_configuration import HttpClientConfiguration
from apimatic_core.utilities.api_helper import ApiHelper
@@ -27,8 +29,8 @@ def __init__(
):
self._http_client_configuration = http_client_configuration
self._global_errors = {}
- self._global_headers = {}
- self._additional_headers = {}
+ self._global_headers = CaseInsensitiveDict()
+ self._additional_headers = CaseInsensitiveDict()
self._auth_managers = {}
self._base_uri_executor = None
diff --git a/apimatic_core/request_builder.py b/apimatic_core/request_builder.py
index 64b6687..d017737 100644
--- a/apimatic_core/request_builder.py
+++ b/apimatic_core/request_builder.py
@@ -1,3 +1,5 @@
+from requests.structures import CaseInsensitiveDict
+
from apimatic_core.exceptions.auth_validation_exception import AuthValidationException
from apimatic_core.http.request.http_request import HttpRequest
from apimatic_core.types.array_serialization_format import SerializationFormats
@@ -15,12 +17,11 @@ def get_param_name(param_value):
def __init__(
self
):
-
self._server = None
self._path = None
self._http_method = None
self._template_params = {}
- self._header_params = {}
+ self._header_params = CaseInsensitiveDict()
self._query_params = {}
self._form_params = {}
self._additional_form_params = {}
@@ -107,10 +108,10 @@ def xml_attributes(self, xml_attributes):
def build(self, global_configuration):
_url = self.process_url(global_configuration)
- _request_headers = self.process_request_headers(global_configuration)
-
_request_body = self.process_body_params()
+ _request_headers = self.process_request_headers(global_configuration)
+
_multipart_params = self.process_multipart_params()
http_request = HttpRequest(http_method=self._http_method,
@@ -147,14 +148,19 @@ def process_request_headers(self, global_configuration):
additional_headers = global_configuration.get_additional_headers()
if global_headers:
- prepared_headers = {key: str(value) if value is not None else value
- for key, value in self._header_params.items()}
- request_headers = {**global_headers, **prepared_headers}
+ request_headers = {**global_headers, **self._header_params}
if additional_headers:
request_headers.update(additional_headers)
- return request_headers
+ serialized_headers = CaseInsensitiveDict(
+ {
+ key: ApiHelper.json_serialize(value)
+ if value is not None else value
+ for key, value in request_headers.items()
+ }
+ )
+ return serialized_headers
def process_body_params(self):
if self._xml_attributes:
diff --git a/setup.py b/setup.py
index c620b96..4d51d1e 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@
setup(
name='apimatic-core',
- version='0.2.18',
+ version='0.2.19',
description='A library that contains core logic and utilities for '
'consuming REST APIs using Python SDKs generated by APIMatic.',
long_description=long_description,
diff --git a/tests/apimatic_core/request_builder_tests/test_request_builder.py b/tests/apimatic_core/request_builder_tests/test_request_builder.py
index 7f84b20..ddbd78f 100644
--- a/tests/apimatic_core/request_builder_tests/test_request_builder.py
+++ b/tests/apimatic_core/request_builder_tests/test_request_builder.py
@@ -176,62 +176,68 @@ def test_additional_query_params(self, input_additional_query_params_value, expe
assert http_request.query_url == 'http://localhost:3000/test?{}'.format(expected_additional_query_params_value)
@pytest.mark.parametrize('input_local_header_param_value, expected_local_header_param_value', [
- ('string', {'header_param': 'string'}),
- (500, {'header_param': 500}),
- (500.12, {'header_param': 500.12}),
- (str(date(1994, 2, 13)), {'header_param': '1994-02-13'}),
+ ('string', {'header': 'string'}),
+ (200, {'header': '200'}),
+ (200.12, {'header': '200.12'}),
+ (str(date(1994, 2, 13)), {'header': '1994-02-13'}),
(ApiHelper.UnixDateTime.from_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': 761117415}),
+ {'header': '761117415'}),
(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ {'header': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
- ([1, 2, 3, 4], {'header_param': [1, 2, 3, 4]})
+ {'header': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ ([1, 2, 3, 4], {'header': '[1, 2, 3, 4]'}),
+ ({'alpha': 'val', 'beta': 'val'}, {'header': '{"alpha": "val", "beta": "val"}'}),
+ (Base.employee_model(), {'header': ApiHelper.json_serialize(Base.employee_model())})
])
def test_local_headers(self, input_local_header_param_value, expected_local_header_param_value):
http_request = self.new_request_builder \
.header_param(Parameter()
- .key('header_param')
+ .key('header')
.value(input_local_header_param_value)) \
.build(self.global_configuration)
assert http_request.headers == expected_local_header_param_value
@pytest.mark.parametrize('input_global_header_param_value, expected_global_header_param_value', [
- ('my-string', {'header_param': 'my-string'}),
- (5000, {'header_param': 5000}),
- (5000.12, {'header_param': 5000.12}),
- (str(date(1998, 2, 13)), {'header_param': '1998-02-13'}),
+ ('my-string', {'global_header': 'my-string'}),
+ (5000, {'global_header': '5000'}),
+ (5000.12, {'global_header': '5000.12'}),
+ (str(date(1998, 2, 13)), {'global_header': '1998-02-13'}),
(ApiHelper.UnixDateTime.from_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': 761117415}),
+ {'global_header': '761117415'}),
(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ {'global_header': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
- ([100, 200, 300, 400], {'header_param': [100, 200, 300, 400]})
+ {'global_header': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ ([100, 200, 300, 400], {'global_header': '[100, 200, 300, 400]'}),
+ ({'key1': 'val1', 'key2': 'val2'}, {'global_header': '{"key1": "val1", "key2": "val2"}'}),
+ (Base.employee_model(), {'global_header': ApiHelper.json_serialize(Base.employee_model())})
])
def test_global_headers(self, input_global_header_param_value, expected_global_header_param_value):
http_request = self.new_request_builder \
.build(self.global_configuration
- .global_header('header_param', input_global_header_param_value))
+ .global_header('global_header', input_global_header_param_value))
assert http_request.headers == expected_global_header_param_value
@pytest.mark.parametrize('input_additional_header_param_value, expected_additional_header_param_value', [
- ('my-string', {'header_param': 'my-string'}),
- (5000, {'header_param': 5000}),
- (5000.12, {'header_param': 5000.12}),
- (str(date(1998, 2, 13)), {'header_param': '1998-02-13'}),
+ ('my-string', {'additional_header': 'my-string'}),
+ (2000, {'additional_header': '2000'}),
+ (2000.12, {'additional_header': '2000.12'}),
+ (str(date(1998, 2, 13)), {'additional_header': '1998-02-13'}),
(ApiHelper.UnixDateTime.from_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': 761117415}),
+ {'additional_header': '761117415'}),
(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ {'additional_header': '{}'.format(Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)),
- {'header_param': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
- ([100, 200, 300, 400], {'header_param': [100, 200, 300, 400]})
+ {'additional_header': '{}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))}),
+ ([100, 200, 300, 400], {'additional_header': '[100, 200, 300, 400]'}),
+ ({'alpha': 'val1', 'bravo': 'val2'}, {'additional_header': '{"alpha": "val1", "bravo": "val2"}'}),
+ (Base.employee_model(), {'additional_header': ApiHelper.json_serialize(Base.employee_model())})
])
def test_additional_headers(self, input_additional_header_param_value, expected_additional_header_param_value):
http_request = self.new_request_builder \
.build(self.global_configuration
- .additional_header('header_param', input_additional_header_param_value))
+ .additional_header('additional_header', input_additional_header_param_value))
assert http_request.headers == expected_additional_header_param_value
@pytest.mark.parametrize('input_global_header_param_value,'
@@ -510,7 +516,7 @@ def test_file_as_body_param(self, input_body_param_value, expected_body_param_va
actual_body_param_value = http_request.parameters
assert actual_body_param_value.read() == expected_body_param_value.read() \
- and http_request.headers['content-type'] == expected_content_type
+ and http_request.headers['Content-Type'] == expected_content_type
finally:
actual_body_param_value.close()
expected_body_param_value.close()