odyn 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. odyn-0.1.0/.github/workflows/ci.yml +62 -0
  2. odyn-0.1.0/.github/workflows/publish.yml +32 -0
  3. odyn-0.1.0/.gitignore +194 -0
  4. odyn-0.1.0/.pre-commit-config.yaml +63 -0
  5. odyn-0.1.0/.python-version +1 -0
  6. odyn-0.1.0/LICENSE +21 -0
  7. odyn-0.1.0/PKG-INFO +253 -0
  8. odyn-0.1.0/README.md +242 -0
  9. odyn-0.1.0/Taskfile.yml +25 -0
  10. odyn-0.1.0/docs/advanced/configuration.md +192 -0
  11. odyn-0.1.0/docs/advanced/logging.md +198 -0
  12. odyn-0.1.0/docs/contributing.md +120 -0
  13. odyn-0.1.0/docs/faq.md +111 -0
  14. odyn-0.1.0/docs/getting-started.md +168 -0
  15. odyn-0.1.0/docs/index.md +81 -0
  16. odyn-0.1.0/docs/installation.md +191 -0
  17. odyn-0.1.0/docs/troubleshooting.md +138 -0
  18. odyn-0.1.0/docs/usage/exceptions.md +207 -0
  19. odyn-0.1.0/docs/usage/odyn.md +438 -0
  20. odyn-0.1.0/docs/usage/sessions.md +332 -0
  21. odyn-0.1.0/examples/01_basic_setup.py +54 -0
  22. odyn-0.1.0/examples/02_authentication_methods.py +159 -0
  23. odyn-0.1.0/examples/03_odata_queries.py +236 -0
  24. odyn-0.1.0/examples/04_error_handling.py +340 -0
  25. odyn-0.1.0/examples/05_business_scenarios.py +375 -0
  26. odyn-0.1.0/examples/06_advanced_configuration.py +319 -0
  27. odyn-0.1.0/examples/07_integration_patterns.py +482 -0
  28. odyn-0.1.0/examples/08_testing_examples.py +372 -0
  29. odyn-0.1.0/examples/README.md +348 -0
  30. odyn-0.1.0/examples/run_all_examples.py +127 -0
  31. odyn-0.1.0/pyproject.toml +310 -0
  32. odyn-0.1.0/scripts/test.bat +108 -0
  33. odyn-0.1.0/scripts/test.sh +94 -0
  34. odyn-0.1.0/src/odyn/__init__.py +32 -0
  35. odyn-0.1.0/src/odyn/_client.py +327 -0
  36. odyn-0.1.0/src/odyn/_exceptions.py +30 -0
  37. odyn-0.1.0/src/odyn/py.typed +0 -0
  38. odyn-0.1.0/src/odyn/sessions.py +150 -0
  39. odyn-0.1.0/tests/test_client.py +384 -0
  40. odyn-0.1.0/tests/test_package.py +41 -0
  41. odyn-0.1.0/tests/test_sessions.py +127 -0
  42. odyn-0.1.0/uv.lock +412 -0
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop, master ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v6
26
+ with:
27
+ version: "latest"
28
+
29
+ - name: Cache dependencies
30
+ uses: actions/cache@v4
31
+ with:
32
+ path: |
33
+ .uv-cache
34
+ .venv
35
+ key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml') }}
36
+ restore-keys: |
37
+ ${{ runner.os }}-uv-
38
+
39
+ - name: Install dependencies
40
+ run: |
41
+ uv sync --dev
42
+
43
+ - name: Run linting
44
+ run: |
45
+ uv run ruff check src/ tests/
46
+ uv run ruff format --check src/ tests/
47
+
48
+ - name: Run type checking
49
+ run: |
50
+ uv run ty check src/
51
+
52
+ - name: Run tests with coverage
53
+ run: |
54
+ uv run pytest --cov=odyn --cov-report=xml --cov-report=term-missing
55
+
56
+ - name: Upload coverage to Codecov (oidc)
57
+ uses: codecov/codecov-action@v5
58
+ with:
59
+ fail_ci_if_error: false
60
+ verbose: true
61
+ token: ${{ secrets.CODECOV_TOKEN }}
62
+ slug: konspec/odyn
@@ -0,0 +1,32 @@
1
+ name: Publish Python 🐍 distribution 📦 to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ name: Build and publish Python 🐍 distribution 📦 to PyPI
11
+ runs-on: ubuntu-latest
12
+ environment: pypi-publish
13
+ permissions:
14
+ id-token: write
15
+
16
+ steps:
17
+ - name: Checkout repository
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+
28
+ - name: Build package
29
+ run: uv build --out-dir dist/
30
+
31
+ - name: Publish package distributions to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
odyn-0.1.0/.gitignore ADDED
@@ -0,0 +1,194 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Abstra
171
+ # Abstra is an AI-powered process automation framework.
172
+ # Ignore directories containing user credentials, local state, and settings.
173
+ # Learn more at https://abstra.io/docs
174
+ .abstra/
175
+
176
+ # Visual Studio Code
177
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
178
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
180
+ # you could uncomment the following to ignore the enitre vscode folder
181
+ # .vscode/
182
+
183
+ # Ruff stuff:
184
+ .ruff_cache/
185
+
186
+ # PyPI configuration file
187
+ .pypirc
188
+
189
+ # Cursor
190
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
191
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
192
+ # refer to https://docs.cursor.com/context/ignore-files
193
+ .cursorignore
194
+ .cursorindexingignore
@@ -0,0 +1,63 @@
1
+ # Pre-commit configuration for odyn project
2
+ # See https://pre-commit.com for more information
3
+
4
+ repos:
5
+ # Python code formatting and linting
6
+ - repo: https://github.com/astral-sh/ruff-pre-commit
7
+ rev: v0.12.0
8
+ hooks:
9
+ - id: ruff-check
10
+ name: ruff-check
11
+ description: Run Ruff linter
12
+ args: [--fix]
13
+ types_or: [python, pyi]
14
+ - id: ruff-format
15
+ name: ruff-format
16
+ description: Run Ruff formatter
17
+ types_or: [python, pyi]
18
+
19
+ # General file checks and cleanup
20
+ - repo: https://github.com/pre-commit/pre-commit-hooks
21
+ rev: v5.0.0
22
+ hooks:
23
+ - id: check-ast
24
+ name: check-ast
25
+ description: Check Python AST validity
26
+ - id: check-yaml
27
+ name: check-yaml
28
+ description: Check YAML file syntax
29
+ - id: check-toml
30
+ name: check-toml
31
+ description: Check TOML file syntax
32
+ - id: check-json
33
+ name: check-json
34
+ description: Check JSON file syntax
35
+ - id: end-of-file-fixer
36
+ name: end-of-file-fixer
37
+ description: Ensure files end with newline
38
+ - id: trailing-whitespace
39
+ name: trailing-whitespace
40
+ description: Remove trailing whitespace
41
+ - id: check-added-large-files
42
+ name: check-added-large-files
43
+ description: Prevent large files from being committed
44
+ args: [--maxkb=1000]
45
+ - id: check-merge-conflict
46
+ name: check-merge-conflict
47
+ description: Check for merge conflict markers
48
+ - id: mixed-line-ending
49
+ name: mixed-line-ending
50
+ description: Check for mixed line endings
51
+ args: [--fix=lf]
52
+
53
+ # Commit message formatting and validation
54
+ - repo: https://github.com/commitizen-tools/commitizen
55
+ rev: v4.8.3
56
+ hooks:
57
+ - id: commitizen
58
+ name: commitizen-check
59
+ description: Check commit message format
60
+ - id: commitizen-branch
61
+ name: commitizen-branch
62
+ description: Check branch naming convention
63
+ stages: [pre-push]
@@ -0,0 +1 @@
1
+ 3.12
odyn-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Konkan Speciality Polyproducts Private Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
odyn-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: odyn
3
+ Version: 0.1.0
4
+ Summary: Odyn – Python adapter for Microsoft Dynamics 365 Business Central OData v4 API
5
+ Author-email: kon-fin <erpsupport@konspec.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: loguru>=0.7.3
9
+ Requires-Dist: requests>=2.32.4
10
+ Description-Content-Type: text/markdown
11
+
12
+ <p align="center">
13
+ <img src="https://konspec.com/wp-content/uploads/2024/05/Konspec-web1.png" alt="Konspec Logo" width="320"/>
14
+ </p>
15
+
16
+ # Odyn
17
+
18
+ **A modern, typed, and robust Python client for the Microsoft Dynamics 365 Business Central OData V4 API**
19
+
20
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
21
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
22
+ [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](docs/index.md)
23
+ [![Tests](https://github.com/konspec/odyn/workflows/CI/badge.svg)](https://github.com/konspec/odyn/actions)
24
+ [![codecov](https://codecov.io/gh/konspec/odyn/graph/badge.svg?token=H8MK6DP96P)](https://codecov.io/gh/konspec/odyn)
25
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
26
+
27
+ ---
28
+
29
+ Odyn provides a convenient and feature-rich interface for interacting with Microsoft Dynamics 365 Business Central, including automatic retry mechanisms, pagination handling, and pluggable authentication sessions.
30
+
31
+ ## Features
32
+
33
+ - **Type Safety**: Fully typed with comprehensive type annotations for better IDE support and runtime safety.
34
+ - **Automatic Retry Logic**: Built-in exponential backoff retry mechanism for handling transient network failures.
35
+ - **Smart Pagination**: Automatic handling of OData pagination with transparent multi-page data retrieval.
36
+ - **Flexible Authentication**: Pluggable `requests.Session` based authentication. Comes with `BearerAuthSession` and `BasicAuthSession` out of the box.
37
+ - **Comprehensive Logging**: Detailed logging with `loguru` integration for easy debugging and monitoring.
38
+ - **Production Ready**: Robust error handling, validation, and timeout management.
39
+ - **Extensible Design**: Easily extendable to support custom authentication strategies or other session-level features.
40
+
41
+ ## Quick Install
42
+
43
+ ```bash
44
+ pip install odyn
45
+ ```
46
+
47
+ Or see [full installation instructions](docs/installation.md) for pip, uv, and poetry.
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from odyn import Odyn, BearerAuthSession
53
+
54
+ # Create an authenticated session
55
+ session = BearerAuthSession("your-access-token")
56
+
57
+ # Initialize the client
58
+ client = Odyn(
59
+ base_url="https://your-tenant.businesscentral.dynamics.com/api/v2.0/",
60
+ session=session
61
+ )
62
+
63
+ # Fetch data with automatic pagination
64
+ customers = client.get("customers")
65
+ print(f"Retrieved {len(customers)} customers")
66
+
67
+ # Use OData query parameters
68
+ filtered_customers = client.get(
69
+ "customers",
70
+ params={
71
+ "$top": 10,
72
+ "$filter": "contains(name, 'Adventure')",
73
+ "$select": "id,name,phoneNumber"
74
+ }
75
+ )
76
+ ```
77
+
78
+ ## Requirements
79
+
80
+ - **Python 3.12+**
81
+ - **requests** (≥2.32.4)
82
+ - **loguru** (≥0.7.3)
83
+
84
+ ## Documentation
85
+
86
+ - 📚 **Full documentation:** [docs/index.md](docs/index.md)
87
+
88
+ ### Getting Started
89
+ - [Installation](docs/installation.md) - Install Odyn using pip, uv, or poetry
90
+ - [Getting Started](docs/getting-started.md) - Quick setup and first API call
91
+
92
+ ### Usage Guides
93
+ - [Odyn Client](docs/usage/odyn.md) - Complete API reference for the main client
94
+ - [Authentication Sessions](docs/usage/sessions.md) - Session management and authentication
95
+ - [Exception Handling](docs/usage/exceptions.md) - Understanding and handling errors
96
+
97
+ ### Advanced Topics
98
+ - [Configuration](docs/advanced/configuration.md) - Timeouts, retries, and advanced settings
99
+ - [Logging](docs/advanced/logging.md) - Logging behavior and customization
100
+
101
+ ### Reference
102
+ - [FAQ](docs/faq.md) - Frequently asked questions
103
+ - [Troubleshooting](docs/troubleshooting.md) - Common issues and solutions
104
+
105
+ ## Examples
106
+
107
+ ### Bearer Token Authentication
108
+ ```python
109
+ from odyn import Odyn, BearerAuthSession
110
+
111
+ # Create a session with your bearer token
112
+ session = BearerAuthSession(token="your-access-token")
113
+
114
+ # Initialize the client
115
+ client = Odyn(
116
+ base_url="https://api.businesscentral.dynamics.com/v2.0/your-tenant-id/production/",
117
+ session=session,
118
+ )
119
+
120
+ # Get all customers with automatic pagination handling
121
+ customers = client.get("customers")
122
+ print(f"Retrieved {len(customers)} customers")
123
+
124
+ # Get the top 10 items, filtering by name and selecting specific fields
125
+ items = client.get(
126
+ "items",
127
+ params={
128
+ "$top": 10,
129
+ "$filter": "contains(displayName, 'Desk')",
130
+ "$select": "id,displayName,itemCategoryCode",
131
+ },
132
+ )
133
+ print(f"Filtered items: {items}")
134
+ ```
135
+
136
+ ### Basic Authentication
137
+ ```python
138
+ from odyn import Odyn, BasicAuthSession
139
+
140
+ # Create a session with your username and web service access key
141
+ session = BasicAuthSession(username="your-username", password="your-web-service-access-key")
142
+
143
+ # Initialize the client
144
+ client = Odyn(
145
+ base_url="https://api.businesscentral.dynamics.com/v2.0/your-tenant-id/production/",
146
+ session=session,
147
+ )
148
+
149
+ # Get all vendors
150
+ vendors = client.get("vendors")
151
+ print(f"Retrieved {len(vendors)} vendors")
152
+ ```
153
+
154
+ ### Advanced Configuration
155
+ ```python
156
+ from odyn import Odyn, BearerAuthSession
157
+ from loguru import logger
158
+
159
+ # Bind a custom component to the logger for easy filtering
160
+ custom_logger = logger.bind(component="business-central-client")
161
+
162
+ # Create a session with custom retry settings
163
+ # This example uses a more aggressive retry strategy than the default.
164
+ session = BearerAuthSession(
165
+ token="your-token",
166
+ retries=10,
167
+ backoff_factor=0.5,
168
+ status_forcelist=[408, 429, 500, 502, 503, 504],
169
+ )
170
+
171
+ # Initialize a client with a custom timeout and the custom logger
172
+ client = Odyn(
173
+ base_url="https://api.businesscentral.dynamics.com/v2.0/your-tenant-id/production/",
174
+ session=session,
175
+ logger=custom_logger,
176
+ timeout=(10, 60), # 10s connect timeout, 60s read timeout
177
+ )
178
+ ```
179
+
180
+ ### Error Handling
181
+ ```python
182
+ from odyn import (
183
+ Odyn,
184
+ BearerAuthSession,
185
+ InvalidURLError,
186
+ InvalidSessionError,
187
+ InvalidTimeoutError,
188
+ InvalidLoggerError,
189
+ )
190
+ import requests
191
+
192
+ try:
193
+ # Intentionally create an invalid session
194
+ session = BearerAuthSession(token=None) # type: ignore
195
+ client = Odyn(base_url="not-a-valid-url", session=session)
196
+ client.get("customers")
197
+
198
+ except InvalidURLError as e:
199
+ print(f"Invalid URL: {e}")
200
+ except InvalidSessionError as e:
201
+ print(f"Invalid session: {e}")
202
+ except InvalidTimeoutError as e:
203
+ print(f"Invalid timeout configuration: {e}")
204
+ except InvalidLoggerError as e:
205
+ print(f"Invalid logger object provided: {e}")
206
+ except requests.exceptions.HTTPError as e:
207
+ # Handle HTTP errors (e.g., 401 Unauthorized, 404 Not Found)
208
+ print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
209
+ except requests.exceptions.RequestException as e:
210
+ # Handle network-related errors (e.g., connection refused)
211
+ print(f"Network Error: {e}")
212
+ except Exception as e:
213
+ print(f"An unexpected error occurred: {e}")
214
+ ```
215
+
216
+ ## Contributing
217
+
218
+ We welcome contributions! Please see our [Contributing Guide](docs/contributing.md) for details.
219
+
220
+ ### Development Setup
221
+
222
+ ```bash
223
+ # Clone the repository
224
+ git clone https://github.com/konspec/odyn.git
225
+ cd odyn
226
+
227
+ # Install dependencies (we recommend using uv)
228
+ uv pip install -e .[dev]
229
+
230
+ # Run tests
231
+ pytest
232
+
233
+ # Run linting
234
+ ruff check .
235
+ ```
236
+
237
+ ## License
238
+
239
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
240
+
241
+ ## Support
242
+
243
+ - [Documentation](docs/index.md)
244
+ - [FAQ](docs/faq.md)
245
+ - [Troubleshooting](docs/troubleshooting.md)
246
+ - [Issues](https://github.com/konspec/odyn/issues)
247
+
248
+ ## Related
249
+
250
+ - [Microsoft Dynamics 365 Business Central OData Web Services](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/odata-web-services)
251
+ - [OData V4 Specification](https://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html)
252
+ - [requests](https://docs.python-requests.org/) - HTTP library
253
+ - [loguru](https://loguru.readthedocs.io/) - Logging library