edgarparser-sdk 0.1.0__tar.gz → 0.1.2__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 (21) hide show
  1. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/PKG-INFO +10 -5
  2. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/README.md +9 -4
  3. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/pyproject.toml +2 -0
  4. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/src/edgarparser/__init__.py +1 -1
  5. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/src/edgarparser/_manifest.py +8 -20
  6. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/src/edgarparser/client.py +9 -30
  7. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/test_client.py +7 -9
  8. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/test_import.py +1 -1
  9. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/test_manifest_alignment.py +0 -1
  10. edgarparser_sdk-0.1.2/tests/test_packaging.py +71 -0
  11. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/test_request_core.py +1 -1
  12. edgarparser_sdk-0.1.0/tests/test_packaging.py +0 -44
  13. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/.gitignore +0 -0
  14. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/LICENSE +0 -0
  15. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/examples/filing_search.py +0 -0
  16. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/examples/financials.py +0 -0
  17. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/examples/metric_series.py +0 -0
  18. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/src/edgarparser/errors.py +0 -0
  19. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/src/edgarparser/py.typed +0 -0
  20. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/conftest.py +0 -0
  21. {edgarparser_sdk-0.1.0 → edgarparser_sdk-0.1.2}/tests/test_endpoint_methods.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edgarparser-sdk
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for the hosted EdgarParser SEC filings API
5
5
  Project-URL: Homepage, https://www.edgarparser.com
6
6
  Project-URL: Documentation, https://docs.edgarparser.com
@@ -23,6 +23,10 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  Python SDK for the hosted EdgarParser API.
25
25
 
26
+ **Status:** Living reference
27
+ **Current authority:** `packages/edgarparser/src/edgarparser/` (`client.py`, `__init__.py`, `_manifest.py`) and `packages/edgarparser/pyproject.toml`
28
+ **Last verified:** 2026-07-29
29
+
26
30
  ```bash
27
31
  pip install edgarparser-sdk
28
32
  ```
@@ -36,9 +40,9 @@ print(client)
36
40
 
37
41
  ## Status
38
42
 
39
- This package is being implemented in the EdgarParser source repo and is not
40
- published yet. The current local package exports `EdgarClient`, typed SDK
41
- errors, and the first v1 hosted-API endpoint methods.
43
+ `edgarparser-sdk` is published on PyPI as the first-party Python client for the
44
+ hosted EdgarParser API. The package exports `EdgarClient`, typed SDK errors,
45
+ and the v1 hosted-API endpoint methods listed below.
42
46
 
43
47
  ## Package Boundary
44
48
 
@@ -71,7 +75,8 @@ from edgarparser import EdgarClient
71
75
  client = EdgarClient()
72
76
  ```
73
77
 
74
- Use `base_url` for staging or local API testing:
78
+ Default `base_url` is `https://www.edgarparser.com`. Override it for staging
79
+ or local API testing:
75
80
 
76
81
  ```python
77
82
  client = EdgarClient(api_key="edgar_...", base_url="http://127.0.0.1:8000")
@@ -2,6 +2,10 @@
2
2
 
3
3
  Python SDK for the hosted EdgarParser API.
4
4
 
5
+ **Status:** Living reference
6
+ **Current authority:** `packages/edgarparser/src/edgarparser/` (`client.py`, `__init__.py`, `_manifest.py`) and `packages/edgarparser/pyproject.toml`
7
+ **Last verified:** 2026-07-29
8
+
5
9
  ```bash
6
10
  pip install edgarparser-sdk
7
11
  ```
@@ -15,9 +19,9 @@ print(client)
15
19
 
16
20
  ## Status
17
21
 
18
- This package is being implemented in the EdgarParser source repo and is not
19
- published yet. The current local package exports `EdgarClient`, typed SDK
20
- errors, and the first v1 hosted-API endpoint methods.
22
+ `edgarparser-sdk` is published on PyPI as the first-party Python client for the
23
+ hosted EdgarParser API. The package exports `EdgarClient`, typed SDK errors,
24
+ and the v1 hosted-API endpoint methods listed below.
21
25
 
22
26
  ## Package Boundary
23
27
 
@@ -50,7 +54,8 @@ from edgarparser import EdgarClient
50
54
  client = EdgarClient()
51
55
  ```
52
56
 
53
- Use `base_url` for staging or local API testing:
57
+ Default `base_url` is `https://www.edgarparser.com`. Override it for staging
58
+ or local API testing:
54
59
 
55
60
  ```python
56
61
  client = EdgarClient(api_key="edgar_...", base_url="http://127.0.0.1:8000")
@@ -33,9 +33,11 @@ dev = [
33
33
  path = "src/edgarparser/__init__.py"
34
34
 
35
35
  [tool.hatch.build.targets.wheel]
36
+ core-metadata-version = "2.4"
36
37
  packages = ["src/edgarparser"]
37
38
 
38
39
  [tool.hatch.build.targets.sdist]
40
+ core-metadata-version = "2.4"
39
41
  include = [
40
42
  "src/edgarparser/",
41
43
  "examples/",
@@ -27,4 +27,4 @@ __all__ = [
27
27
  "__version__",
28
28
  ]
29
29
 
30
- __version__ = "0.1.0"
30
+ __version__ = "0.1.2"
@@ -12,7 +12,6 @@ class SDKMethodSpec:
12
12
  api_method: str
13
13
  api_path: str
14
14
  contract_version: str
15
- schema_fingerprint: str
16
15
  required_params: tuple[str, ...] = ()
17
16
  optional_params: tuple[str, ...] = ()
18
17
  path_params: tuple[str, ...] = ()
@@ -25,8 +24,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
25
24
  tool_name="get_financials",
26
25
  api_method="GET",
27
26
  api_path="/api/financials",
28
- contract_version="1.0.0",
29
- schema_fingerprint="sha256:8c2b5486165eb2628dfb324c558c1148a48a46453f4102f0491e16503316b1c5",
27
+ contract_version="2.0.0",
30
28
  required_params=("ticker", "year", "quarter"),
31
29
  optional_params=("full_year_mode", "source"),
32
30
  ),
@@ -35,8 +33,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
35
33
  tool_name="get_metric",
36
34
  api_method="GET",
37
35
  api_path="/api/metric",
38
- contract_version="1.0.0",
39
- schema_fingerprint="sha256:0350ec893a5d3a2a2510dc059711a93f6ec9665a0e068b32d8c4b2d8cb8accd8",
36
+ contract_version="2.0.0",
40
37
  required_params=("ticker", "year", "quarter", "metric_name"),
41
38
  optional_params=("full_year_mode", "source", "date_type", "role"),
42
39
  ),
@@ -45,8 +42,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
45
42
  tool_name="get_metric_series",
46
43
  api_method="GET",
47
44
  api_path="/api/metric/series",
48
- contract_version="1.1.0",
49
- schema_fingerprint="sha256:dff62ae8bf4eb8c648776ac91e02e324c83a1cccbf3beeefcb7855272970a906",
45
+ contract_version="2.0.0",
50
46
  required_params=("ticker", "metric_name", "end_year", "end_quarter"),
51
47
  optional_params=(
52
48
  "periods",
@@ -64,8 +60,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
64
60
  tool_name="list_metrics",
65
61
  api_method="GET",
66
62
  api_path="/api/financials/list_metrics",
67
- contract_version="1.0.0",
68
- schema_fingerprint="sha256:07341bd265a257b8c90ee99b42ecef054e6eaa16c2f341f91ffae287bfe907d8",
63
+ contract_version="1.1.0",
69
64
  required_params=("ticker", "year", "quarter"),
70
65
  optional_params=(
71
66
  "full_year_mode",
@@ -80,8 +75,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
80
75
  tool_name="search_metrics",
81
76
  api_method="GET",
82
77
  api_path="/api/financials/search_metrics",
83
- contract_version="1.0.0",
84
- schema_fingerprint="sha256:381c0bf743038780d55c447b0c7103da0e22d13c33b866c73130b7265071853c",
78
+ contract_version="1.1.0",
85
79
  required_params=("ticker", "year", "quarter", "query"),
86
80
  optional_params=(
87
81
  "full_year_mode",
@@ -97,8 +91,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
97
91
  tool_name="get_statement",
98
92
  api_method="GET",
99
93
  api_path="/api/statement",
100
- contract_version="1.0.0",
101
- schema_fingerprint="sha256:6da91e176e78fe536130ed3ea11cf9b8dfccf923cbbfc47483d7cb2108383986",
94
+ contract_version="2.0.0",
102
95
  required_params=("ticker", "statement"),
103
96
  optional_params=(
104
97
  "year",
@@ -116,7 +109,6 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
116
109
  api_method="GET",
117
110
  api_path="/api/filings",
118
111
  contract_version="1.0.0",
119
- schema_fingerprint="sha256:ffa7e669b836cbbc181bc337cb55219e55bf9a5c75ce3d27040a8aad25f8c993",
120
112
  required_params=("ticker", "year", "quarter"),
121
113
  optional_params=("source",),
122
114
  ),
@@ -125,8 +117,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
125
117
  tool_name="get_filing_document",
126
118
  api_method="GET",
127
119
  api_path="/api/filing/document",
128
- contract_version="1.0.0",
129
- schema_fingerprint="sha256:4d559aded5c9760ccb498944a984a128254a76349348de297da30ee338ff123a",
120
+ contract_version="1.1.0",
130
121
  optional_params=(
131
122
  "ticker",
132
123
  "year",
@@ -147,8 +138,7 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
147
138
  tool_name="search_filing_text",
148
139
  api_method="GET",
149
140
  api_path="/api/filing/text/search",
150
- contract_version="1.0.0",
151
- schema_fingerprint="sha256:be08198a156e55118e9b32a0fb41ead07906469059fb27d3010f1a1f5a9c0c8c",
141
+ contract_version="1.1.0",
152
142
  required_params=("ticker", "year", "quarter", "query"),
153
143
  optional_params=("source",),
154
144
  ),
@@ -158,7 +148,6 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
158
148
  api_method="POST",
159
149
  api_path="/api/warm",
160
150
  contract_version="1.0.0",
161
- schema_fingerprint="sha256:1636aba36d166ecf2901e72078fc8a6182f7174e541d9183561392781e8f8cbd",
162
151
  body_params=("items",),
163
152
  ),
164
153
  SDKMethodSpec(
@@ -167,7 +156,6 @@ SDK_METHODS: tuple[SDKMethodSpec, ...] = (
167
156
  api_method="GET",
168
157
  api_path="/api/warm/{job_id}",
169
158
  contract_version="1.0.0",
170
- schema_fingerprint="sha256:f48e0b9a707e404c640822af06d51f3523f54538fed625a24ee06dc220cb3245",
171
159
  path_params=("job_id",),
172
160
  ),
173
161
  )
@@ -468,17 +468,6 @@ class EdgarClient:
468
468
  ")"
469
469
  )
470
470
 
471
- def _debug_state(self) -> dict[str, Any]:
472
- """Return non-secret state for tests and diagnostics."""
473
-
474
- return {
475
- "base_url": self.base_url,
476
- "timeout": self.timeout,
477
- "api_key_set": bool(self.api_key),
478
- "closed": self._closed,
479
- "user_agent": self.user_agent,
480
- }
481
-
482
471
  def _url(self, path: str) -> str:
483
472
  if not path or not path.strip():
484
473
  raise ValueError("path must be a non-empty API path")
@@ -555,7 +544,7 @@ class EdgarClient:
555
544
  ) -> EdgarAPIError:
556
545
  payload = self._response_json(response)
557
546
  message = f"HTTP {response.status_code}"
558
- error_type: str | None = self._default_error_type(response.status_code)
547
+ error_cls, error_type = self._error_classification(response.status_code)
559
548
  details: Any | None = None
560
549
 
561
550
  if isinstance(payload, Mapping):
@@ -575,7 +564,6 @@ class EdgarClient:
575
564
  message = text[:300]
576
565
  details = self._text_details(text)
577
566
 
578
- error_cls = self._error_class(response.status_code)
579
567
  return error_cls(
580
568
  response.status_code,
581
569
  message,
@@ -604,27 +592,18 @@ class EdgarClient:
604
592
  return {**details, "cta": payload["cta"]}
605
593
  return {"details": details, "cta": payload["cta"]}
606
594
 
607
- def _default_error_type(self, status_code: int) -> str | None:
608
- if status_code == 429:
609
- return "rate_limit"
610
- if status_code in {400, 422}:
611
- return "validation_error"
612
- if status_code in {401, 403}:
613
- return "auth_error"
614
- if status_code == 404:
615
- return "not_found"
616
- return None
617
-
618
- def _error_class(self, status_code: int) -> type[EdgarAPIError]:
595
+ def _error_classification(
596
+ self, status_code: int
597
+ ) -> tuple[type[EdgarAPIError], str | None]:
619
598
  if status_code in {401, 403}:
620
- return EdgarAuthError
599
+ return EdgarAuthError, "auth_error"
621
600
  if status_code == 429:
622
- return EdgarRateLimitError
601
+ return EdgarRateLimitError, "rate_limit"
623
602
  if status_code == 404:
624
- return EdgarNotFoundError
603
+ return EdgarNotFoundError, "not_found"
625
604
  if status_code in {400, 422}:
626
- return EdgarValidationError
627
- return EdgarAPIError
605
+ return EdgarValidationError, "validation_error"
606
+ return EdgarAPIError, None
628
607
 
629
608
  def _text_details(self, text: str) -> dict[str, str] | None:
630
609
  if not text:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import pytest
4
4
 
5
- from edgarparser import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, EdgarClient
5
+ from edgarparser import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, EdgarClient, EdgarError
6
6
 
7
7
 
8
8
  class RecordingSession:
@@ -20,7 +20,7 @@ def test_client_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
20
20
 
21
21
  assert client.base_url == DEFAULT_BASE_URL
22
22
  assert client.timeout == DEFAULT_TIMEOUT
23
- assert client.headers() == {"User-Agent": "edgarparser-sdk/0.1.0"}
23
+ assert client.headers() == {"User-Agent": "edgarparser-sdk/0.1.2"}
24
24
  assert "api_key=<unset>" in repr(client)
25
25
 
26
26
 
@@ -56,13 +56,11 @@ def test_client_validates_base_url_and_timeout() -> None:
56
56
  EdgarClient(timeout=0)
57
57
 
58
58
 
59
- def test_client_closes_owned_session() -> None:
59
+ def test_client_close_is_idempotent() -> None:
60
60
  client = EdgarClient(api_key="x")
61
61
  client.close()
62
62
  client.close()
63
63
 
64
- assert client._debug_state()["closed"] is True
65
-
66
64
 
67
65
  def test_client_does_not_close_injected_session() -> None:
68
66
  session = RecordingSession()
@@ -71,11 +69,11 @@ def test_client_does_not_close_injected_session() -> None:
71
69
  client.close()
72
70
 
73
71
  assert session.closed is False
74
- assert client._debug_state()["closed"] is True
75
72
 
76
73
 
77
- def test_context_manager_closes_owned_session() -> None:
74
+ def test_context_manager_rejects_requests_after_exit() -> None:
78
75
  with EdgarClient(api_key="x") as client:
79
- assert client._debug_state()["closed"] is False
76
+ pass
80
77
 
81
- assert client._debug_state()["closed"] is True
78
+ with pytest.raises(EdgarError, match="client is closed"):
79
+ client.get_financials("AAPL", year=2025, quarter=1)
@@ -5,7 +5,7 @@ def test_import_exports_client_and_version() -> None:
5
5
  import edgarparser
6
6
  from edgarparser import EdgarAPIError, EdgarClient, EdgarError
7
7
 
8
- assert edgarparser.__version__ == "0.1.0"
8
+ assert edgarparser.__version__ == "0.1.2"
9
9
  assert EdgarClient.__name__ == "EdgarClient"
10
10
  assert EdgarError.__name__ == "EdgarError"
11
11
  assert issubclass(EdgarAPIError, EdgarError)
@@ -93,7 +93,6 @@ def test_sdk_manifest_matches_tool_manifest_contracts() -> None:
93
93
  assert tool["api_method"] == spec.api_method
94
94
  assert tool["api_path"] == spec.api_path
95
95
  assert tool["contract_version"] == spec.contract_version
96
- assert tool["schema_fingerprint"] == spec.schema_fingerprint
97
96
 
98
97
 
99
98
  def test_sdk_manifest_matches_fastapi_route_contracts() -> None:
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ import tarfile
6
+ import zipfile
7
+ from pathlib import Path
8
+
9
+ import tomllib
10
+
11
+ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
12
+
13
+
14
+ def test_pyproject_uses_package_version_as_source_of_truth() -> None:
15
+ pyproject = tomllib.loads((PACKAGE_ROOT / "pyproject.toml").read_text())
16
+
17
+ assert pyproject["project"]["name"] == "edgarparser-sdk"
18
+ assert pyproject["project"]["dynamic"] == ["version"]
19
+ assert pyproject["tool"]["hatch"]["version"]["path"] == (
20
+ "src/edgarparser/__init__.py"
21
+ )
22
+ assert pyproject["tool"]["hatch"]["build"]["targets"]["wheel"][
23
+ "core-metadata-version"
24
+ ] == "2.4"
25
+ assert pyproject["tool"]["hatch"]["build"]["targets"]["sdist"][
26
+ "core-metadata-version"
27
+ ] == "2.4"
28
+
29
+
30
+ def test_distributions_use_compatible_metadata_and_wheel_contents(
31
+ tmp_path: Path,
32
+ ) -> None:
33
+ subprocess.run(
34
+ [
35
+ sys.executable,
36
+ "-m",
37
+ "build",
38
+ "--outdir",
39
+ str(tmp_path),
40
+ str(PACKAGE_ROOT),
41
+ ],
42
+ check=True,
43
+ capture_output=True,
44
+ text=True,
45
+ )
46
+
47
+ wheel = next(tmp_path.glob("edgarparser_sdk-*.whl"))
48
+ with zipfile.ZipFile(wheel) as archive:
49
+ names = set(archive.namelist())
50
+ metadata_name = next(
51
+ name for name in names if name.endswith(".dist-info/METADATA")
52
+ )
53
+ metadata = archive.read(metadata_name)
54
+
55
+ assert metadata.splitlines()[0] == b"Metadata-Version: 2.4"
56
+ assert "edgarparser/py.typed" in names
57
+ assert any(name.endswith(".dist-info/licenses/LICENSE") for name in names)
58
+
59
+ sdist = next(tmp_path.glob("edgarparser_sdk-*.tar.gz"))
60
+ with tarfile.open(sdist, "r:gz") as archive:
61
+ pkg_info_member = next(
62
+ member
63
+ for member in archive.getmembers()
64
+ if member.name.endswith("/PKG-INFO")
65
+ and len(Path(member.name).parts) == 2
66
+ )
67
+ pkg_info_file = archive.extractfile(pkg_info_member)
68
+ assert pkg_info_file is not None
69
+ pkg_info = pkg_info_file.read()
70
+
71
+ assert pkg_info.splitlines()[0] == b"Metadata-Version: 2.4"
@@ -86,7 +86,7 @@ def test_request_builds_url_headers_params_body_and_timeout() -> None:
86
86
  "method": "GET",
87
87
  "url": "https://api.example.com/api/test",
88
88
  "headers": {
89
- "User-Agent": "edgarparser-sdk/0.1.0",
89
+ "User-Agent": "edgarparser-sdk/0.1.2",
90
90
  "Authorization": "Bearer secret-key",
91
91
  },
92
92
  "params": {"ticker": "AAPL"},
@@ -1,44 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import subprocess
4
- import sys
5
- import zipfile
6
- from pathlib import Path
7
-
8
- import tomllib
9
-
10
- PACKAGE_ROOT = Path(__file__).resolve().parents[1]
11
-
12
-
13
- def test_pyproject_uses_package_version_as_source_of_truth() -> None:
14
- pyproject = tomllib.loads((PACKAGE_ROOT / "pyproject.toml").read_text())
15
-
16
- assert pyproject["project"]["name"] == "edgarparser-sdk"
17
- assert pyproject["project"]["dynamic"] == ["version"]
18
- assert pyproject["tool"]["hatch"]["version"]["path"] == (
19
- "src/edgarparser/__init__.py"
20
- )
21
-
22
-
23
- def test_wheel_contains_typed_marker_and_license(tmp_path: Path) -> None:
24
- subprocess.run(
25
- [
26
- sys.executable,
27
- "-m",
28
- "build",
29
- "--wheel",
30
- "--outdir",
31
- str(tmp_path),
32
- str(PACKAGE_ROOT),
33
- ],
34
- check=True,
35
- capture_output=True,
36
- text=True,
37
- )
38
-
39
- wheel = next(tmp_path.glob("edgarparser_sdk-*.whl"))
40
- with zipfile.ZipFile(wheel) as archive:
41
- names = set(archive.namelist())
42
-
43
- assert "edgarparser/py.typed" in names
44
- assert any(name.endswith(".dist-info/licenses/LICENSE") for name in names)
File without changes