envv 1.0.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.
envv-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: envv
3
+ Version: 1.0.0
4
+ Summary: Official EnvVault Python SDK — centralized secrets management
5
+ Author: EnvVault Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://git.netsphere360.com/envvault/envvault-sdk
8
+ Project-URL: Repository, https://git.netsphere360.com/envvault/envvault-sdk
9
+ Keywords: envvault,secrets,management,sdk,vault
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.28.0
23
+
24
+ # envvault (Python SDK)
25
+
26
+ Official Python SDK for [EnvVault](https://git.netsphere360.com/envvault) — centralized secrets management.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install envvault
32
+ ```
33
+
34
+ **Requires Python >= 3.10.**
35
+
36
+ ## Quick Start
37
+
38
+ ```python
39
+ from envvault import EnvVaultClient
40
+
41
+ client = EnvVaultClient(
42
+ api_url="https://api.envvault.example.com",
43
+ token="your-token",
44
+ )
45
+
46
+ # Or rely on env vars: ENVVAULT_API_URL, ENVVAULT_TOKEN
47
+ # Or config file: ~/.envv/config.json
48
+
49
+ value = client.get_secret("DATABASE_URL")
50
+ secrets = client.list_secrets()
51
+ client.set_secret("API_KEY", "sk-...")
52
+ client.delete_secret("OLD_KEY")
53
+ ```
54
+
55
+ ## Configuration Priority
56
+
57
+ | Priority | Source |
58
+ |----------|--------|
59
+ | 1 (highest) | Constructor args (`api_url`, `token`) |
60
+ | 2 | Environment variables (`ENVVAULT_API_URL`, `ENVVAULT_TOKEN`) |
61
+ | 3 (lowest) | Config file (`~/.envv/config.json`) |
62
+
63
+ ## Error Handling
64
+
65
+ ```python
66
+ from envvault import EnvVaultClient, AuthenticationError, NotFoundError
67
+
68
+ client = EnvVaultClient(api_url="...", token="...")
69
+
70
+ try:
71
+ value = client.get_secret("MY_KEY")
72
+ except AuthenticationError:
73
+ print("Invalid token")
74
+ except NotFoundError:
75
+ print("Secret not found")
76
+ ```
77
+
78
+ ## License
79
+
80
+ [MIT](../../LICENSE)
envv-1.0.0/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # envvault (Python SDK)
2
+
3
+ Official Python SDK for [EnvVault](https://git.netsphere360.com/envvault) — centralized secrets management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install envvault
9
+ ```
10
+
11
+ **Requires Python >= 3.10.**
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from envvault import EnvVaultClient
17
+
18
+ client = EnvVaultClient(
19
+ api_url="https://api.envvault.example.com",
20
+ token="your-token",
21
+ )
22
+
23
+ # Or rely on env vars: ENVVAULT_API_URL, ENVVAULT_TOKEN
24
+ # Or config file: ~/.envv/config.json
25
+
26
+ value = client.get_secret("DATABASE_URL")
27
+ secrets = client.list_secrets()
28
+ client.set_secret("API_KEY", "sk-...")
29
+ client.delete_secret("OLD_KEY")
30
+ ```
31
+
32
+ ## Configuration Priority
33
+
34
+ | Priority | Source |
35
+ |----------|--------|
36
+ | 1 (highest) | Constructor args (`api_url`, `token`) |
37
+ | 2 | Environment variables (`ENVVAULT_API_URL`, `ENVVAULT_TOKEN`) |
38
+ | 3 (lowest) | Config file (`~/.envv/config.json`) |
39
+
40
+ ## Error Handling
41
+
42
+ ```python
43
+ from envvault import EnvVaultClient, AuthenticationError, NotFoundError
44
+
45
+ client = EnvVaultClient(api_url="...", token="...")
46
+
47
+ try:
48
+ value = client.get_secret("MY_KEY")
49
+ except AuthenticationError:
50
+ print("Invalid token")
51
+ except NotFoundError:
52
+ print("Secret not found")
53
+ ```
54
+
55
+ ## License
56
+
57
+ [MIT](../../LICENSE)
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: envv
3
+ Version: 1.0.0
4
+ Summary: Official EnvVault Python SDK — centralized secrets management
5
+ Author: EnvVault Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://git.netsphere360.com/envvault/envvault-sdk
8
+ Project-URL: Repository, https://git.netsphere360.com/envvault/envvault-sdk
9
+ Keywords: envvault,secrets,management,sdk,vault
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: requests>=2.28.0
23
+
24
+ # envvault (Python SDK)
25
+
26
+ Official Python SDK for [EnvVault](https://git.netsphere360.com/envvault) — centralized secrets management.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install envvault
32
+ ```
33
+
34
+ **Requires Python >= 3.10.**
35
+
36
+ ## Quick Start
37
+
38
+ ```python
39
+ from envvault import EnvVaultClient
40
+
41
+ client = EnvVaultClient(
42
+ api_url="https://api.envvault.example.com",
43
+ token="your-token",
44
+ )
45
+
46
+ # Or rely on env vars: ENVVAULT_API_URL, ENVVAULT_TOKEN
47
+ # Or config file: ~/.envv/config.json
48
+
49
+ value = client.get_secret("DATABASE_URL")
50
+ secrets = client.list_secrets()
51
+ client.set_secret("API_KEY", "sk-...")
52
+ client.delete_secret("OLD_KEY")
53
+ ```
54
+
55
+ ## Configuration Priority
56
+
57
+ | Priority | Source |
58
+ |----------|--------|
59
+ | 1 (highest) | Constructor args (`api_url`, `token`) |
60
+ | 2 | Environment variables (`ENVVAULT_API_URL`, `ENVVAULT_TOKEN`) |
61
+ | 3 (lowest) | Config file (`~/.envv/config.json`) |
62
+
63
+ ## Error Handling
64
+
65
+ ```python
66
+ from envvault import EnvVaultClient, AuthenticationError, NotFoundError
67
+
68
+ client = EnvVaultClient(api_url="...", token="...")
69
+
70
+ try:
71
+ value = client.get_secret("MY_KEY")
72
+ except AuthenticationError:
73
+ print("Invalid token")
74
+ except NotFoundError:
75
+ print("Secret not found")
76
+ ```
77
+
78
+ ## License
79
+
80
+ [MIT](../../LICENSE)
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ envv.egg-info/PKG-INFO
4
+ envv.egg-info/SOURCES.txt
5
+ envv.egg-info/dependency_links.txt
6
+ envv.egg-info/requires.txt
7
+ envv.egg-info/top_level.txt
8
+ envvault/__init__.py
9
+ envvault/client.py
10
+ envvault/config.py
11
+ envvault/errors.py
12
+ tests/test_validation.py
@@ -0,0 +1 @@
1
+ requests>=2.28.0
@@ -0,0 +1 @@
1
+ envvault
@@ -0,0 +1,24 @@
1
+ """EnvVault Python SDK — centralized secrets management."""
2
+
3
+ from envvault.client import EnvVaultClient
4
+ from envvault.errors import (
5
+ EnvVaultError,
6
+ AuthenticationError,
7
+ AuthorizationError,
8
+ NotFoundError,
9
+ NetworkError,
10
+ ConfigurationError,
11
+ ValidationError,
12
+ )
13
+
14
+ __version__ = "1.0.0"
15
+ __all__ = [
16
+ "EnvVaultClient",
17
+ "EnvVaultError",
18
+ "AuthenticationError",
19
+ "AuthorizationError",
20
+ "NotFoundError",
21
+ "NetworkError",
22
+ "ConfigurationError",
23
+ "ValidationError",
24
+ ]
@@ -0,0 +1,163 @@
1
+ """EnvVault HTTP client with typed error handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+ from urllib.parse import quote
7
+
8
+ import requests
9
+
10
+ from envvault.config import resolve_config
11
+ from envvault.errors import (
12
+ AuthenticationError,
13
+ AuthorizationError,
14
+ ConfigurationError,
15
+ EnvVaultError,
16
+ NetworkError,
17
+ NotFoundError,
18
+ ValidationError,
19
+ )
20
+
21
+ _DEFAULT_TIMEOUT = 30 # seconds
22
+
23
+
24
+ class EnvVaultClient:
25
+ """EnvVault SDK client for managing secrets.
26
+
27
+ Args:
28
+ api_url: The EnvVault API base URL.
29
+ token: Bearer authentication token.
30
+ timeout: Request timeout in seconds (default 30).
31
+
32
+ Configuration priority: constructor args > env vars > ~/.envv/config.json
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ api_url: Optional[str] = None,
38
+ token: Optional[str] = None,
39
+ timeout: int = _DEFAULT_TIMEOUT,
40
+ ):
41
+ config = resolve_config(api_url=api_url, token=token)
42
+ self._api_url = (config["api_url"] or "").rstrip("/")
43
+ self._token = config["token"]
44
+ self._timeout = timeout
45
+
46
+ if not self._api_url:
47
+ raise ConfigurationError(
48
+ "API URL is not configured. Provide it via constructor, "
49
+ "ENVVAULT_API_URL, or ~/.envv/config.json."
50
+ )
51
+
52
+ self._session = requests.Session()
53
+ self._session.headers.update({
54
+ "Content-Type": "application/json",
55
+ })
56
+ if self._token:
57
+ self._session.headers["Authorization"] = f"Bearer {self._token}"
58
+
59
+ # ── Public API ──────────────────────────────────────────────
60
+
61
+ def get_secret(self, key: str) -> str | None:
62
+ """Fetch a single secret's decrypted value.
63
+
64
+ Args:
65
+ key: The secret key.
66
+
67
+ Returns:
68
+ The decrypted secret value, or None if not present in response.
69
+ """
70
+ _validate_key(key)
71
+ data = self._request("GET", f"/cli/secrets/{quote(key, safe='')}")
72
+ return (data.get("data") or {}).get("value") or data.get("value")
73
+
74
+ def list_secrets(self) -> list[dict[str, Any]]:
75
+ """List all secrets (metadata only, values not included).
76
+
77
+ Returns:
78
+ List of secret metadata dicts.
79
+ """
80
+ data = self._request("GET", "/cli/secrets")
81
+ return data.get("data", data) if isinstance(data, dict) else data
82
+
83
+ def set_secret(self, key: str, value: str) -> bool:
84
+ """Create or update a secret.
85
+
86
+ Args:
87
+ key: The secret key.
88
+ value: The secret value.
89
+
90
+ Returns:
91
+ True on success.
92
+ """
93
+ _validate_key(key)
94
+ _validate_value(value)
95
+ data = self._request("POST", "/cli/secrets", json={"key": key, "value": value})
96
+ return data.get("success", False)
97
+
98
+ def delete_secret(self, key: str) -> bool:
99
+ """Delete a secret by key.
100
+
101
+ Args:
102
+ key: The secret key.
103
+
104
+ Returns:
105
+ True on success.
106
+ """
107
+ _validate_key(key)
108
+ data = self._request("DELETE", f"/cli/secrets/{quote(key, safe='')}")
109
+ return data.get("success", False)
110
+
111
+ # ── Internal ────────────────────────────────────────────────
112
+
113
+ def _request(self, method: str, path: str, **kwargs) -> dict:
114
+ """Execute an HTTP request and handle errors."""
115
+ url = f"{self._api_url}{path}"
116
+ try:
117
+ resp = self._session.request(
118
+ method, url, timeout=self._timeout, **kwargs
119
+ )
120
+ except requests.ConnectionError as exc:
121
+ raise NetworkError(str(exc)) from exc
122
+ except requests.Timeout as exc:
123
+ raise NetworkError(f"Request timed out: {exc}") from exc
124
+ except requests.RequestException as exc:
125
+ raise NetworkError(str(exc)) from exc
126
+
127
+ if resp.status_code >= 400:
128
+ self._raise_for_status(resp)
129
+
130
+ return resp.json()
131
+
132
+ @staticmethod
133
+ def _raise_for_status(resp: requests.Response) -> None:
134
+ """Map HTTP error responses to typed SDK errors."""
135
+ try:
136
+ body = resp.json()
137
+ except ValueError:
138
+ body = {}
139
+
140
+ message = body.get("error") or body.get("message") or resp.text
141
+ status = resp.status_code
142
+
143
+ if status == 401:
144
+ raise AuthenticationError(message)
145
+ if status == 403:
146
+ raise AuthorizationError(message)
147
+ if status == 404:
148
+ raise NotFoundError(message)
149
+
150
+ raise EnvVaultError(f"API Error ({status}): {message}", "API_ERROR", status)
151
+
152
+
153
+ # ── Validators ──────────────────────────────────────────────────
154
+
155
+
156
+ def _validate_key(key: Any) -> None:
157
+ if not isinstance(key, str) or not key.strip():
158
+ raise ValidationError("Secret key must be a non-empty string.")
159
+
160
+
161
+ def _validate_value(value: Any) -> None:
162
+ if not isinstance(value, str):
163
+ raise ValidationError("Secret value must be a string.")
@@ -0,0 +1,59 @@
1
+ """Configuration resolution for the EnvVault SDK.
2
+
3
+ Priority: Programmatic (constructor) > Environment Variables > Config File (~/.envv/config.json)
4
+ """
5
+
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+
12
+ def resolve_config(
13
+ api_url: Optional[str] = None,
14
+ token: Optional[str] = None,
15
+ ) -> dict:
16
+ """Resolve SDK configuration from multiple sources.
17
+
18
+ Args:
19
+ api_url: Programmatic API URL override (highest priority).
20
+ token: Programmatic token override (highest priority).
21
+
22
+ Returns:
23
+ dict with 'api_url' and 'token' keys.
24
+ """
25
+ resolved_api_url = ""
26
+ resolved_token = None
27
+
28
+ # 1. Config file (lowest priority)
29
+ config_path = Path.home() / ".envv" / "config.json"
30
+ try:
31
+ if config_path.is_file():
32
+ with open(config_path, "r", encoding="utf-8") as f:
33
+ file_config = json.load(f)
34
+
35
+ resolved_token = (
36
+ file_config.get("token")
37
+ or (file_config.get("config") or {}).get("token")
38
+ )
39
+ if file_config.get("api_base_url"):
40
+ resolved_api_url = file_config["api_base_url"]
41
+ except (json.JSONDecodeError, OSError):
42
+ pass
43
+
44
+ # 2. Environment variables override config file
45
+ env_token = os.environ.get("ENVVAULT_TOKEN")
46
+ if env_token:
47
+ resolved_token = env_token
48
+
49
+ env_api_url = os.environ.get("ENVVAULT_API_URL")
50
+ if env_api_url:
51
+ resolved_api_url = env_api_url
52
+
53
+ # 3. Programmatic overrides (highest priority)
54
+ if token:
55
+ resolved_token = token
56
+ if api_url:
57
+ resolved_api_url = api_url
58
+
59
+ return {"api_url": resolved_api_url, "token": resolved_token}
@@ -0,0 +1,53 @@
1
+ """Typed error hierarchy for the EnvVault SDK."""
2
+
3
+
4
+ class EnvVaultError(Exception):
5
+ """Base error for all EnvVault SDK errors."""
6
+
7
+ def __init__(self, message: str, code: str, status_code: int | None = None):
8
+ super().__init__(message)
9
+ self.message = message
10
+ self.code = code
11
+ self.status_code = status_code
12
+
13
+
14
+ class AuthenticationError(EnvVaultError):
15
+ """Raised when authentication fails (HTTP 401)."""
16
+
17
+ def __init__(self, message: str = "Invalid or expired token."):
18
+ super().__init__(message, "UNAUTHORIZED", 401)
19
+
20
+
21
+ class AuthorizationError(EnvVaultError):
22
+ """Raised when access is denied (HTTP 403)."""
23
+
24
+ def __init__(self, message: str = "You do not have access to this resource."):
25
+ super().__init__(message, "FORBIDDEN", 403)
26
+
27
+
28
+ class NotFoundError(EnvVaultError):
29
+ """Raised when the requested resource is not found (HTTP 404)."""
30
+
31
+ def __init__(self, message: str = "The requested resource was not found."):
32
+ super().__init__(message, "NOT_FOUND", 404)
33
+
34
+
35
+ class NetworkError(EnvVaultError):
36
+ """Raised on network-level failures."""
37
+
38
+ def __init__(self, message: str = "A network error occurred."):
39
+ super().__init__(message, "NETWORK_ERROR")
40
+
41
+
42
+ class ConfigurationError(EnvVaultError):
43
+ """Raised when required SDK configuration is missing."""
44
+
45
+ def __init__(self, message: str = "SDK is not configured. Provide api_url and token."):
46
+ super().__init__(message, "CONFIGURATION_ERROR")
47
+
48
+
49
+ class ValidationError(EnvVaultError):
50
+ """Raised when a function receives invalid arguments."""
51
+
52
+ def __init__(self, message: str):
53
+ super().__init__(message, "VALIDATION_ERROR")
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "envv"
7
+ version = "1.0.0"
8
+ description = "Official EnvVault Python SDK — centralized secrets management"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "EnvVault Team"},
14
+ ]
15
+ keywords = ["envvault", "secrets", "management", "sdk", "vault"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Security",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+ dependencies = [
29
+ "requests>=2.28.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://git.netsphere360.com/envvault/envvault-sdk"
34
+ Repository = "https://git.netsphere360.com/envvault/envvault-sdk"
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["envvault*"]
envv-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,78 @@
1
+ """Tests for input validation and SDK exports."""
2
+
3
+ import pytest
4
+
5
+ from envvault import (
6
+ EnvVaultClient,
7
+ EnvVaultError,
8
+ AuthenticationError,
9
+ AuthorizationError,
10
+ NotFoundError,
11
+ NetworkError,
12
+ ConfigurationError,
13
+ ValidationError,
14
+ )
15
+
16
+
17
+ class TestExports:
18
+ """Verify all expected symbols are importable."""
19
+
20
+ def test_client_class_exists(self):
21
+ assert callable(EnvVaultClient)
22
+
23
+ def test_error_classes_exist(self):
24
+ for cls in (
25
+ EnvVaultError,
26
+ AuthenticationError,
27
+ AuthorizationError,
28
+ NotFoundError,
29
+ NetworkError,
30
+ ConfigurationError,
31
+ ValidationError,
32
+ ):
33
+ assert issubclass(cls, Exception)
34
+
35
+ def test_error_hierarchy(self):
36
+ assert issubclass(AuthenticationError, EnvVaultError)
37
+ assert issubclass(AuthorizationError, EnvVaultError)
38
+ assert issubclass(NotFoundError, EnvVaultError)
39
+ assert issubclass(NetworkError, EnvVaultError)
40
+ assert issubclass(ConfigurationError, EnvVaultError)
41
+ assert issubclass(ValidationError, EnvVaultError)
42
+
43
+
44
+ class TestValidation:
45
+ """Verify input validation raises ValidationError."""
46
+
47
+ def _make_client(self):
48
+ """Create a client that will fail on HTTP but pass validation."""
49
+ return EnvVaultClient(api_url="http://localhost:9999", token="test")
50
+
51
+ def test_get_secret_empty_key(self):
52
+ client = self._make_client()
53
+ with pytest.raises(ValidationError):
54
+ client.get_secret("")
55
+
56
+ def test_get_secret_non_string_key(self):
57
+ client = self._make_client()
58
+ with pytest.raises(ValidationError):
59
+ client.get_secret(123)
60
+
61
+ def test_set_secret_empty_key(self):
62
+ client = self._make_client()
63
+ with pytest.raises(ValidationError):
64
+ client.set_secret("", "value")
65
+
66
+ def test_set_secret_non_string_value(self):
67
+ client = self._make_client()
68
+ with pytest.raises(ValidationError):
69
+ client.set_secret("key", 123)
70
+
71
+ def test_delete_secret_whitespace_key(self):
72
+ client = self._make_client()
73
+ with pytest.raises(ValidationError):
74
+ client.delete_secret(" ")
75
+
76
+ def test_missing_api_url_raises_config_error(self):
77
+ with pytest.raises(ConfigurationError):
78
+ EnvVaultClient(api_url="", token="t")