Metadata-Version: 2.4
Name: veeam-br
Version: 0.5.0
Summary: Veeam Backup & Replication REST API wrapper for Python
Author-email: Jonah May <jonah@mayfamily.me>, Maurice Kevenaar <maurice@kevenaar.name>
License: Apache-2.0
Project-URL: Homepage, https://github.com/Cenvora/veeam-br
Project-URL: Repository, https://github.com/Cenvora/veeam-br
Project-URL: Documentation, https://github.com/Cenvora/veeam-br#readme
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<0.29.0,>=0.23.0
Requires-Dist: attrs>=22.2.0
Requires-Dist: python-dateutil>=2.8.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Dynamic: license-file

<h1 align="center">
<br>
<img src="https://raw.githubusercontent.com/Cenvora/veeam-br/main/media/Veeam_logo_2024_RGB_main_20.png"
     alt="Veeam Logo"
     height="100">
<br>
<br>
Veeam Backup & Replication Python API Wrapper
</h1>

<h4 align="center">
Python package for interacting with the Veeam Backup & Replication REST API
</h4>

<!-- Summary -->
This project is an independent, open source Python client for the Veeam Backup & Replication <a href="https://helpcenter.veeam.com/references/vbr/13/rest/1.3-rev1/tag/SectionAbout">REST API</a>. It is not affiliated with, endorsed by, or sponsored by Veeam Software.
<!-- Summary -->

## Supported Versions

<table>
  <thead>
    <tr>
      <th>VBR Version</th>
      <th>API Version</th>
      <th>Supported</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>13.1.0.411</td>
      <td>1.3-rev2</td>
      <td style="text-align:center;">&#9989;</td>
    </tr>
    <tr>
      <td>13.0.1.180</td>
      <td>1.3-rev1</td>
      <td style="text-align:center;">&#9989;</td>
    </tr>
    <tr>
      <td>13.0.0.4967</td>
      <td>1.3-rev0</td>
      <td style="text-align:center;">&#9989;</td>
    </tr>
    <tr>
      <td>12.3.1.1139</td>
      <td>1.2-rev1</td>
      <td style="text-align:center;">&#9989;</td>
    </tr>
    <tr>
      <td>&lt; 12.3.1.1139</td>
      <td>&lt; 1.2-rev1</td>
      <td style="text-align:center;">&#10060;</td>
    </tr>
  </tbody>
</table>

## How to support new API versions
1. Download the OpenAPI schema into openapi_schemas
2. Install the openapi-python-client package
3. Run `python fix_openapi_yaml.py .\openapi_schemas\vbr_rest_{version}.json .\openapi_schemas\vbr_rest_{version}_fixed.json` 
4. Run `openapi-python-client generate --path ".\openapi_schemas\vbr_rest_{version}_fixed.json" --output-path ".\veeam_br" --overwrite`
5. Fix any warnings/errors
6. Rename the folder to match the API version (i.e., `v1.3-rev1`)
7. Add the version mapping to versions.py
8. Write pytest tests
9. If an older API has been deprecated, delete its folder, json, and version.py entry, then update the supported versions section of the readme

## Install
### From PyPi
`pip install veeam-br`


### From Source
Clone the repository and install dependencies:
```sh
git clone https://github.com/Cenvora/veeam-br.git
cd veeam-br
pip install -e .
```

## Usage
### Recommended Usage (Smart Client)
The `VeeamClient` handles:
- API version routing
- Authentication
- Token refresh
- `x-api-version` header injection so package API version matches header values
- Async calls
- Operation discovery

Each packaged version can be called independently through separate imports, but this is the <strong>recommended way</strong>  to use this library.

#### Create a client and connect
```python
import asyncio
from veeam_br.client import VeeamClient

async def main():
    vc = VeeamClient(
        host="https://vbr.example.com:9419",
        username="administrator",
        password="SuperSecretPassword",
        api_version="1.3-rev1",
        verify_ssl=False,
    )

    await vc.connect()

    # use the client...

    await vc.close()

asyncio.run(main())
```

#### Detect the API version a server serves

The REST API has no endpoint that reports its supported versions, and Veeam's guidance is
that the caller picks one. What a server does expose is a Swagger document per version it
serves, so `detect_api_version` probes those and returns the newest version that both the
server serves and this library can speak:

```python
import asyncio
from veeam_br.client import VeeamClient
from veeam_br.discovery import detect_api_version

async def main():
    base_url = "https://vbr.example.com:9419"

    api_version = await detect_api_version(base_url, verify_ssl=False)
    if api_version is None:
        # Swagger may be unreachable, disabled or gated — choose your own default
        api_version = "1.3-rev1"

    vc = VeeamClient(
        host=base_url,
        username="administrator",
        password="SuperSecretPassword",
        api_version=api_version,
        verify_ssl=False,
    )
    await vc.connect()

asyncio.run(main())
```

Detection needs no credentials, so it can run before you have any. Probes are concurrent, so
it costs roughly one round trip regardless of how many versions this library supports.

If you do not know the port either, `detect_rest_api` finds both at once. Veeam B&R 13.1
serves the REST API on 443 and no longer needs a dedicated port; 9419 still answers on 13.1
and is the only port on older releases, but Veeam has said it will be removed in a future
release:

```python
from veeam_br.discovery import detect_rest_api

endpoint = await detect_rest_api("vbr.example.com", verify_ssl=False)
if endpoint:
    print(endpoint.port, endpoint.api_version)  # e.g. 443 1.3-rev2
    base_url = f"https://vbr.example.com{endpoint.base_url_suffix}"
```

Ports are tried in preference order — 443 before 9419 by default, since a 13.1 server answers
on both — and the newest version served by the winning port is returned.

Resolve it once and store the result rather than detecting on every start: a server upgrade
would otherwise silently move you onto a newer revision, and revisions rename enum values and
add required fields.

#### Call an API endpoint (async)
```python
repos = await vc.call(
    vc.api("repositories").get_all_repositories
)
```

#### Filter certain object types
Some objects, such as SmartObjectS3, use polymorphic subtypes with circular inheritance. These tend to not play well with package creation tools, so as part of the import process into this project, those relationships are broken. Where a repository would normally have a `bucket` object with immutability information inside, this breakage instead causes `bucket` to always be `UNSET` and instead populates the data into `additional_properties`. For example:
```python
from veeam_br.v1_3_rev1.models.e_repository_type import ERepositoryType

smart_s3 = [
    r for r in repos.data
    if r.type_ == ERepositoryType.SMARTOBJECTS3
]
```

So to access bucket & immutability data:
```python
for repo in smart_s3:
    bucket = repo.additional_properties.get("bucket", {})
    immutability = bucket.get("immutability", {})

    print({
        "name": repo.name,
        "bucket": bucket.get("bucketName"),
        "days": immutability.get("daysCount"),
        "enabled": immutability.get("isEnabled"),
    })
```

Until `openapi-python-client` figures out and implements a way around this or Veeam rewrites their OpenAPI schemas to not use circular references/inheritance, this is a known limitation.

#### Call any endpoint
Operations map directly to the OpenAPI layout:
```markdown
api/
└── repositories/
    └── get_all_repositories.py
```

Call it like this:
```python
await vc.call(
    vc.api("repositories").get_all_repositories
)
```

Or explicity:
```python
await vc.call(
    vc.api("repositories.get_all_repositories")
)
```

#### Names that shadow Python builtins
`openapi-python-client` appends a trailing underscore to any generated identifier that would shadow a Python builtin or keyword. The `license` tag is the one that shows up in practice, so its namespace is `license_`:
```python
await vc.call(
    vc.api("license_").get_installed_license
)
```
This is the same rule behind the `type_` attribute used in the filtering example above.

#### Pagination example
```python
result = await vc.call(
    vc.api("repositories").get_all_repositories,
    limit=50,
    skip=0,
)
```

#### Close the client
```python
await vc.close()
```

## Contributing
Contributions are welcome! To contribute:
- Fork the repository
- Create a feature branch
- Make your changes and add tests
- Submit a pull request with a clear description

Please follow PEP8 style and include docstrings for new functions/classes.

## 🤝 Core Contributors
This project is made possible thanks to the efforts of our core contributors:

- [Jonah May](https://github.com/JonahMMay)  
- [Maurice Kevenaar](https://github.com/mkevenaar)  

We’re grateful for their continued support and contributions.
