fulcrumpro 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.
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: fulcrumpro
3
+ Version: 0.1.0
4
+ Summary: A lightweight Python SDK for the Fulcrum Pro API, built with `httpx`.
5
+ Requires-Python: >=3.14
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.28.1
8
+
9
+ # FulcrumPro
10
+
11
+ A lightweight Python SDK for the [Fulcrum Pro](https://fulcrumpro.com) API, built on [`httpx`](https://www.python-httpx.org/).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install fulcrumpro
17
+ ```
18
+
19
+ Or with `uv`:
20
+
21
+ ```bash
22
+ uv add fulcrumpro
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Python 3.14+
28
+
29
+ ## Quick Start
30
+
31
+ ```python
32
+ from fulcrumpro import FulcrumPro
33
+
34
+ client = FulcrumPro(api_token="your-api-token")
35
+
36
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
37
+ print(job["name"])
38
+ ```
39
+
40
+ Use as a context manager to ensure the underlying HTTP connection is closed:
41
+
42
+ ```python
43
+ with FulcrumPro(api_token="your-api-token") as client:
44
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
45
+ ```
46
+
47
+ ## Authentication
48
+
49
+ All requests are authenticated with a Bearer token. Pass your API token when constructing the client:
50
+
51
+ ```python
52
+ client = FulcrumPro(api_token="your-api-token")
53
+ ```
54
+
55
+ ## Resources
56
+
57
+ ### Jobs
58
+
59
+ ```python
60
+ # Get a job by ID
61
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
62
+ ```
63
+
64
+ ## Error Handling
65
+
66
+ All non-2xx responses raise a `FulcrumProError`:
67
+
68
+ ```python
69
+ from fulcrumpro import FulcrumPro, FulcrumProError
70
+
71
+ client = FulcrumPro(api_token="your-api-token")
72
+
73
+ try:
74
+ job = client.jobs.get("nonexistent-id")
75
+ except FulcrumProError as e:
76
+ print(e.status_code) # e.g. 404
77
+ print(str(e)) # "FulcrumPro API error 404: not found"
78
+ ```
79
+
80
+ ## Version History
81
+
82
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,74 @@
1
+ # FulcrumPro
2
+
3
+ A lightweight Python SDK for the [Fulcrum Pro](https://fulcrumpro.com) API, built on [`httpx`](https://www.python-httpx.org/).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install fulcrumpro
9
+ ```
10
+
11
+ Or with `uv`:
12
+
13
+ ```bash
14
+ uv add fulcrumpro
15
+ ```
16
+
17
+ ## Requirements
18
+
19
+ - Python 3.14+
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from fulcrumpro import FulcrumPro
25
+
26
+ client = FulcrumPro(api_token="your-api-token")
27
+
28
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
29
+ print(job["name"])
30
+ ```
31
+
32
+ Use as a context manager to ensure the underlying HTTP connection is closed:
33
+
34
+ ```python
35
+ with FulcrumPro(api_token="your-api-token") as client:
36
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
37
+ ```
38
+
39
+ ## Authentication
40
+
41
+ All requests are authenticated with a Bearer token. Pass your API token when constructing the client:
42
+
43
+ ```python
44
+ client = FulcrumPro(api_token="your-api-token")
45
+ ```
46
+
47
+ ## Resources
48
+
49
+ ### Jobs
50
+
51
+ ```python
52
+ # Get a job by ID
53
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
54
+ ```
55
+
56
+ ## Error Handling
57
+
58
+ All non-2xx responses raise a `FulcrumProError`:
59
+
60
+ ```python
61
+ from fulcrumpro import FulcrumPro, FulcrumProError
62
+
63
+ client = FulcrumPro(api_token="your-api-token")
64
+
65
+ try:
66
+ job = client.jobs.get("nonexistent-id")
67
+ except FulcrumProError as e:
68
+ print(e.status_code) # e.g. 404
69
+ print(str(e)) # "FulcrumPro API error 404: not found"
70
+ ```
71
+
72
+ ## Version History
73
+
74
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,3 @@
1
+ from fulcrumpro.client import FulcrumPro, FulcrumProError
2
+
3
+ __all__ = ["FulcrumPro", "FulcrumProError"]
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from fulcrumpro.endpoints.jobs import Jobs
8
+
9
+
10
+ class FulcrumProError(Exception):
11
+ def __init__(self, status_code: int, message: str):
12
+ self.status_code = status_code
13
+ super().__init__(f"FulcrumPro API error {status_code}: {message}")
14
+
15
+
16
+ class FulcrumPro:
17
+ def __init__(
18
+ self,
19
+ api_token: str,
20
+ ):
21
+ base_url = "https://api.fulcrumpro.com/api"
22
+ self._client = httpx.Client(
23
+ base_url=base_url,
24
+ headers={
25
+ "Content-Type": "application/json",
26
+ "Authorization": f"Bearer {api_token}",
27
+ },
28
+ )
29
+ self.jobs = Jobs(self)
30
+
31
+ def _request(self, method: str, path: str, **kwargs: Any) -> dict:
32
+ response = self._client.request(method, path, **kwargs)
33
+ if not response.is_success:
34
+ data = response.json()
35
+ raise FulcrumProError(response.status_code, data.get("errors"))
36
+ return response.json()
37
+
38
+ def get(self, path: str, **kwargs: Any) -> dict:
39
+ return self._request("GET", path, **kwargs)
40
+
41
+ def post(self, path: str, **kwargs: Any) -> dict:
42
+ return self._request("POST", path, **kwargs)
43
+
44
+ def patch(self, path: str, **kwargs: Any) -> dict:
45
+ return self._request("PATCH", path, **kwargs)
46
+
47
+ def delete(self, path: str, **kwargs: Any) -> dict:
48
+ return self._request("DELETE", path, **kwargs)
49
+
50
+ def __enter__(self) -> FulcrumPro:
51
+ return self
52
+
53
+ def __exit__(self, *_: Any) -> None:
54
+ self._client.close()
File without changes
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from fulcrumpro.client import FulcrumPro
7
+
8
+
9
+ class Jobs:
10
+ def __init__(self, client: FulcrumPro):
11
+ self._client = client
12
+
13
+ def get(self, job_id: str) -> dict[str, Any]:
14
+ """
15
+ Get a job by ID.
16
+
17
+ :param job_id: The ID of the job.
18
+
19
+ :return: The job data.
20
+ """
21
+ return self._client.get(f"/jobs/{job_id}")
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: fulcrumpro
3
+ Version: 0.1.0
4
+ Summary: A lightweight Python SDK for the Fulcrum Pro API, built with `httpx`.
5
+ Requires-Python: >=3.14
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.28.1
8
+
9
+ # FulcrumPro
10
+
11
+ A lightweight Python SDK for the [Fulcrum Pro](https://fulcrumpro.com) API, built on [`httpx`](https://www.python-httpx.org/).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install fulcrumpro
17
+ ```
18
+
19
+ Or with `uv`:
20
+
21
+ ```bash
22
+ uv add fulcrumpro
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Python 3.14+
28
+
29
+ ## Quick Start
30
+
31
+ ```python
32
+ from fulcrumpro import FulcrumPro
33
+
34
+ client = FulcrumPro(api_token="your-api-token")
35
+
36
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
37
+ print(job["name"])
38
+ ```
39
+
40
+ Use as a context manager to ensure the underlying HTTP connection is closed:
41
+
42
+ ```python
43
+ with FulcrumPro(api_token="your-api-token") as client:
44
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
45
+ ```
46
+
47
+ ## Authentication
48
+
49
+ All requests are authenticated with a Bearer token. Pass your API token when constructing the client:
50
+
51
+ ```python
52
+ client = FulcrumPro(api_token="your-api-token")
53
+ ```
54
+
55
+ ## Resources
56
+
57
+ ### Jobs
58
+
59
+ ```python
60
+ # Get a job by ID
61
+ job = client.jobs.get("6a0e5cad7c2e97225f8def9c")
62
+ ```
63
+
64
+ ## Error Handling
65
+
66
+ All non-2xx responses raise a `FulcrumProError`:
67
+
68
+ ```python
69
+ from fulcrumpro import FulcrumPro, FulcrumProError
70
+
71
+ client = FulcrumPro(api_token="your-api-token")
72
+
73
+ try:
74
+ job = client.jobs.get("nonexistent-id")
75
+ except FulcrumProError as e:
76
+ print(e.status_code) # e.g. 404
77
+ print(str(e)) # "FulcrumPro API error 404: not found"
78
+ ```
79
+
80
+ ## Version History
81
+
82
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ fulcrumpro/__init__.py
4
+ fulcrumpro/client.py
5
+ fulcrumpro.egg-info/PKG-INFO
6
+ fulcrumpro.egg-info/SOURCES.txt
7
+ fulcrumpro.egg-info/dependency_links.txt
8
+ fulcrumpro.egg-info/requires.txt
9
+ fulcrumpro.egg-info/top_level.txt
10
+ fulcrumpro/endpoints/__init__.py
11
+ fulcrumpro/endpoints/jobs.py
12
+ tests/test_client.py
@@ -0,0 +1 @@
1
+ httpx>=0.28.1
@@ -0,0 +1 @@
1
+ fulcrumpro
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "fulcrumpro"
3
+ version = "0.1.0"
4
+ description = "A lightweight Python SDK for the Fulcrum Pro API, built with `httpx`."
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ dependencies = [
8
+ "httpx>=0.28.1",
9
+ ]
10
+
11
+ [dependency-groups]
12
+ dev = [
13
+ "pytest>=9.0.3",
14
+ "pytest-cov>=7.1.0",
15
+ "rich>=15.0.0",
16
+ "ruff>=0.15.14",
17
+ ]
18
+
19
+ [tool.ruff]
20
+ line-length = 88
21
+ target-version = "py312"
22
+ src = ["infra"]
23
+
24
+ [tool.ruff.lint]
25
+ select = [
26
+ "E", # pycodestyle errors
27
+ "W", # pycodestyle warnings
28
+ "F", # pyflakes — unused imports, undefined names
29
+ "I", # isort — import ordering
30
+ "B", # flake8-bugbear — common bugs and design issues
31
+ "C4", # flake8-comprehensions — better list/dict/set comprehensions
32
+ "UP", # pyupgrade — modernize Python syntax
33
+ "SIM", # flake8-simplify — simplifiable code
34
+ "TCH", # flake8-type-checking — type hint import hygiene
35
+ "RUF", # ruff-specific rules
36
+ "N", # pep8-naming conventions
37
+ "S", # flake8-bandit — security issues
38
+ ]
39
+
40
+ ignore = [
41
+ "S101", # assert statements — needed for pytest
42
+ "B008", # function calls in default arguments — CDK uses this pattern
43
+ "N802", # function name lowercase — CDK construct names are PascalCase
44
+ ]
45
+
46
+ [tool.ruff.lint.per-file-ignores]
47
+ "tests/**" = [
48
+ "S101", # assert is fine in tests
49
+ "S105", # hardcoded passwords fine in tests
50
+ "S106",
51
+ ]
52
+ "cdk/**" = [
53
+ "N803", # CDK uses PascalCase for some args
54
+ ]
55
+
56
+ [tool.ruff.lint.isort]
57
+ force-sort-within-sections = true
58
+
59
+ [tool.ruff.format]
60
+ quote-style = "double"
61
+ indent-style = "space"
62
+
63
+ [tool.pytest.ini_options]
64
+ pythonpath = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from fulcrumpro.client import FulcrumPro, FulcrumProError
4
+ import pytest
5
+
6
+ from .conftest import make_response
7
+
8
+
9
+ class TestInit:
10
+ def test_sets_auth_header(self, client: FulcrumPro):
11
+ headers = dict(client._client.headers)
12
+ assert headers["authorization"] == "Bearer test-token"
13
+
14
+ def test_sets_content_type_header(self, client: FulcrumPro):
15
+ headers = dict(client._client.headers)
16
+ assert headers["content-type"] == "application/json"
17
+
18
+ def test_exposes_jobs_resource(self, client: FulcrumPro):
19
+ from fulcrumpro.endpoints.jobs import Jobs
20
+
21
+ assert isinstance(client.jobs, Jobs)
22
+
23
+
24
+ class TestRequest:
25
+ def test_returns_parsed_json_on_success(self, client: FulcrumPro):
26
+ client._client.request.return_value = make_response(200, {"id": "abc"})
27
+ result = client._request("GET", "/jobs/abc")
28
+ assert result == {"id": "abc"}
29
+
30
+ def test_calls_underlying_client_with_method_and_path(self, client: FulcrumPro):
31
+ client._client.request.return_value = make_response(200, {})
32
+ client._request("GET", "/jobs/abc")
33
+ client._client.request.assert_called_once_with("GET", "/jobs/abc")
34
+
35
+ def test_raises_fulcrumpro_error_on_4xx(self, client: FulcrumPro):
36
+ client._client.request.return_value = make_response(
37
+ 404, {"errors": "not found"}
38
+ )
39
+ with pytest.raises(FulcrumProError) as exc_info:
40
+ client._request("GET", "/jobs/missing")
41
+ assert exc_info.value.status_code == 404
42
+ assert "not found" in str(exc_info.value)
43
+
44
+ def test_raises_fulcrumpro_error_on_5xx(self, client: FulcrumPro):
45
+ client._client.request.return_value = make_response(500, {"errors": "boom"})
46
+ with pytest.raises(FulcrumProError) as exc_info:
47
+ client._request("GET", "/jobs/abc")
48
+ assert exc_info.value.status_code == 500
49
+
50
+ def test_propagates_json_decode_error_on_non_json_response(
51
+ self, client: FulcrumPro
52
+ ):
53
+ from json import JSONDecodeError
54
+
55
+ client._client.request.return_value = make_response(502, text="Bad Gateway")
56
+ with pytest.raises(JSONDecodeError):
57
+ client._request("GET", "/jobs/abc")
58
+
59
+ def test_passes_extra_kwargs_to_underlying_client(self, client: FulcrumPro):
60
+ client._client.request.return_value = make_response(200, {})
61
+ client._request("POST", "/jobs", json={"name": "test"})
62
+ client._client.request.assert_called_once_with(
63
+ "POST", "/jobs", json={"name": "test"}
64
+ )
65
+
66
+
67
+ class TestHttpMethods:
68
+ def test_get_delegates_to_request(self, client: FulcrumPro):
69
+ client._client.request.return_value = make_response(200, {"ok": True})
70
+ result = client.get("/jobs/1")
71
+ client._client.request.assert_called_once_with("GET", "/jobs/1")
72
+ assert result == {"ok": True}
73
+
74
+ def test_post_delegates_to_request(self, client: FulcrumPro):
75
+ client._client.request.return_value = make_response(201, {"id": "new"})
76
+ result = client.post("/jobs", json={"name": "new job"})
77
+ client._client.request.assert_called_once_with(
78
+ "POST", "/jobs", json={"name": "new job"}
79
+ )
80
+ assert result == {"id": "new"}
81
+
82
+ def test_patch_delegates_to_request(self, client: FulcrumPro):
83
+ client._client.request.return_value = make_response(200, {"id": "1"})
84
+ result = client.patch("/jobs/1", json={"name": "updated"})
85
+ client._client.request.assert_called_once_with(
86
+ "PATCH", "/jobs/1", json={"name": "updated"}
87
+ )
88
+ assert result == {"id": "1"}
89
+
90
+ def test_delete_delegates_to_request(self, client: FulcrumPro):
91
+ client._client.request.return_value = make_response(200, {})
92
+ result = client.delete("/jobs/1")
93
+ client._client.request.assert_called_once_with("DELETE", "/jobs/1")
94
+ assert result == {}
95
+
96
+
97
+ class TestContextManager:
98
+ def test_enter_returns_client(self, client: FulcrumPro):
99
+ assert client.__enter__() is client
100
+
101
+ def test_exit_closes_underlying_httpx_client(self, client: FulcrumPro):
102
+ client.__exit__(None, None, None)
103
+ client._client.close.assert_called_once()