Skip to content

Commit a9792d8

Browse files
authored
Add type hints and py.typed marker (PEP 561) (#112)
Annotate the whole codebase and ship a py.typed marker so type checkers pick up the inline hints. Each module uses 'from __future__ import annotations' so annotations stay lazy and cannot affect runtime behavior (and remain valid on Python 3.8).
1 parent 161e0f4 commit a9792d8

13 files changed

Lines changed: 244 additions & 171 deletions

‎.github/workflows/ci-build.yml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@ jobs:
3030
- name: Install dependencies
3131
run: |
3232
python -m pip install --upgrade pip
33-
pip install --upgrade flake8 pytest pycodestyle pytest-cov pytest-mock
33+
pip install --upgrade flake8 pytest pycodestyle pytest-cov pytest-mock "mypy==2.3.*"
3434
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
3535
- name: Lint with flake8
3636
run: |
3737
# stop the build if there are Python syntax errors or undefined names
3838
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
3939
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
4040
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
41+
- name: Type check with mypy
42+
if: matrix.python-version == '3.11'
43+
run: |
44+
mypy grobid_client
4145
- name: Test with pytest
4246
run: |
4347
pytest tests/ -v --cov=grobid_client --cov-report=xml --cov-report=term-missing

‎MANIFEST.in‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
include Readme.md
1+
include Readme.md
2+
include grobid_client/py.typed

‎Readme.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ concurrent processing capabilities for PDF documents, reference strings, and pat
3333
- **Sentence Segmentation**: Layout-aware sentence segmentation capabilities
3434
- **JSON Output**: Convert TEI XML output to structured JSON format with CORD-19-like structure
3535
- **Markdown Output**: Convert TEI XML output to clean Markdown format with structured sections
36+
- **Type Hints**: Ships inline type annotations and a `py.typed` marker (PEP 561) for static type checking
3637

3738
## 📋 Prerequisites
3839

‎grobid_client/client.py‎

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
""" Generic API Client """
2+
from __future__ import annotations
3+
24
from copy import deepcopy
35
import json
6+
from typing import Any, Optional, Tuple
7+
48
import requests
59

610
try:
@@ -16,12 +20,17 @@ class ApiClient(object):
1620
service methods, i.e. ``get``, ``post``, ``put`` and ``delete``.
1721
"""
1822

19-
accept_type = "application/xml"
20-
api_base = None
23+
accept_type: str = "application/xml"
24+
api_base: Optional[str] = None
2125

2226
def __init__(
23-
self, base_url, username=None, api_key=None, status_endpoint=None, timeout=60
24-
):
27+
self,
28+
base_url: str,
29+
username: Optional[str] = None,
30+
api_key: Optional[str] = None,
31+
status_endpoint: Optional[str] = None,
32+
timeout: int = 60,
33+
) -> None:
2534
"""Initialise client.
2635
2736
Args:
@@ -37,7 +46,7 @@ def __init__(
3746
self.timeout = timeout
3847

3948
@staticmethod
40-
def encode(request, data):
49+
def encode(request: Any, data: Optional[dict]) -> Any:
4150
"""Add request content data to request body, set Content-type header.
4251
4352
Should be overridden by subclasses if not using JSON encoding.
@@ -58,7 +67,7 @@ def encode(request, data):
5867
return request
5968

6069
@staticmethod
61-
def decode(response):
70+
def decode(response: Any) -> Any:
6271
"""Decode the returned data in the response.
6372
6473
Should be overridden by subclasses if something else than JSON is
@@ -73,9 +82,9 @@ def decode(response):
7382
try:
7483
return response.json()
7584
except ValueError as e:
76-
return e.message
85+
return e.message # type: ignore[attr-defined] # pre-existing (Python 2 style)
7786

78-
def get_credentials(self):
87+
def get_credentials(self) -> dict:
7988
"""Returns parameters to be added to authenticate the request.
8089
8190
This lives on its own to make it easier to re-implement it if needed.
@@ -87,14 +96,14 @@ def get_credentials(self):
8796

8897
def call_api(
8998
self,
90-
method,
91-
url,
92-
headers=None,
93-
params=None,
94-
data=None,
95-
files=None,
96-
timeout=None,
97-
):
99+
method: str,
100+
url: str,
101+
headers: Optional[dict] = None,
102+
params: Optional[dict] = None,
103+
data: Optional[dict] = None,
104+
files: Optional[dict] = None,
105+
timeout: Optional[int] = None,
106+
) -> Tuple[requests.Response, int]:
98107
"""Call API.
99108
100109
This returns object containing data, with error details if applicable.
@@ -130,7 +139,7 @@ def call_api(
130139

131140
return r, r.status_code
132141

133-
def get(self, url, params=None, **kwargs):
142+
def get(self, url: str, params: Optional[dict] = None, **kwargs: Any) -> Tuple[requests.Response, int]:
134143
"""Call the API with a GET request.
135144
136145
Args:
@@ -142,7 +151,7 @@ def get(self, url, params=None, **kwargs):
142151
"""
143152
return self.call_api("GET", url, params=params, **kwargs)
144153

145-
def delete(self, url, params=None, **kwargs):
154+
def delete(self, url: str, params: Optional[dict] = None, **kwargs: Any) -> Tuple[requests.Response, int]:
146155
"""Call the API with a DELETE request.
147156
148157
Args:
@@ -154,7 +163,14 @@ def delete(self, url, params=None, **kwargs):
154163
"""
155164
return self.call_api("DELETE", url, params=params, **kwargs)
156165

157-
def put(self, url, params=None, data=None, files=None, **kwargs):
166+
def put(
167+
self,
168+
url: str,
169+
params: Optional[dict] = None,
170+
data: Optional[dict] = None,
171+
files: Optional[dict] = None,
172+
**kwargs: Any,
173+
) -> Tuple[requests.Response, int]:
158174
"""Call the API with a PUT request.
159175
160176
Args:
@@ -170,7 +186,14 @@ def put(self, url, params=None, data=None, files=None, **kwargs):
170186
"PUT", url, params=params, data=data, files=files, **kwargs
171187
)
172188

173-
def post(self, url, params=None, data=None, files=None, **kwargs):
189+
def post(
190+
self,
191+
url: str,
192+
params: Optional[dict] = None,
193+
data: Optional[dict] = None,
194+
files: Optional[dict] = None,
195+
**kwargs: Any,
196+
) -> Tuple[requests.Response, int]:
174197
"""Call the API with a POST request.
175198
176199
Args:
@@ -186,7 +209,7 @@ def post(self, url, params=None, data=None, files=None, **kwargs):
186209
method="POST", url=url, params=params, data=data, files=files, **kwargs
187210
)
188211

189-
def service_status(self, **kwargs):
212+
def service_status(self, **kwargs: Any) -> Tuple[requests.Response, int]:
190213
"""Call the API to get the status of the service.
191214
192215
Returns:

0 commit comments

Comments
 (0)