Skip to content

Commit ed76a46

Browse files
committed
trying to fix warnings
1 parent edf14ab commit ed76a46

File tree

12 files changed

+237
-192
lines changed

12 files changed

+237
-192
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "biothings-typed-client"
3-
version = "0.0.4"
3+
version = "0.0.5"
44
description = "A strongly-typed Python wrapper around the BioThings Client library, providing type safety and better IDE support through Python's type hints and Pydantic models."
55
readme = "README.md"
66
authors = [
@@ -30,5 +30,5 @@ exclude = [
3030
[dependency-groups]
3131
dev = [
3232
"pytest>=8.3.5",
33-
"pytest-asyncio>=0.26.0",
33+
"pytest-asyncio>=1.0.0",
3434
]

src/biothings_typed_client/abstract_client.py

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -299,15 +299,49 @@ def _response_model(self) -> type[T]:
299299
raise NotImplementedError("Subclasses must implement _response_model")
300300

301301
class AbstractClientAsync(Generic[T]):
302-
"""Abstract base class for BioThings clients (asynchronous)"""
302+
"""Abstract base class for BioThings clients (asynchronous)
303+
304+
For proper caching setup, use as an async context manager:
305+
async with ClientAsync() as client:
306+
result = await client.get("some_id")
307+
308+
Or manually enable caching after instantiation:
309+
client = ClientAsync()
310+
await client.set_caching()
311+
result = await client.get("some_id")
312+
"""
303313

304314
def __init__(self, api_name: str, caching: bool = True):
305315
self._client = get_async_client(api_name)
306316
self._closed = False
317+
self._enable_caching = caching
318+
319+
@classmethod
320+
async def create(cls, caching: bool = True):
321+
"""
322+
Create and initialize an async client with caching properly set up.
323+
324+
This is a convenience factory method for users who don't want to use
325+
the async context manager pattern.
326+
327+
Args:
328+
caching: Whether to enable caching
329+
330+
Returns:
331+
Fully initialized async client
332+
333+
Example:
334+
client = await GeneClientAsync.create(caching=True)
335+
result = await client.get("some_id")
336+
"""
337+
instance = cls(caching=False) # Don't auto-enable caching
307338
if caching:
308-
self._client.set_caching()
339+
await instance.set_caching()
340+
return instance
309341

310342
async def __aenter__(self):
343+
if self._enable_caching:
344+
await self.set_caching()
311345
return self
312346

313347
async def __aexit__(self, exc_type, exc_val, exc_tb):
@@ -326,21 +360,13 @@ async def close(self):
326360

327361
def __del__(self):
328362
"""Cleanup when the object is deleted"""
329-
if not self._closed and hasattr(self._client, 'close'):
330-
import asyncio
331-
try:
332-
loop = asyncio.get_event_loop()
333-
if loop.is_running():
334-
# If loop is running, create a task to close the client
335-
loop.create_task(self._client.close())
336-
else:
337-
# If loop is not running, run the close operation
338-
loop.run_until_complete(self._client.close())
339-
except Exception:
340-
# Ignore any errors during cleanup
341-
pass
342-
finally:
343-
self._closed = True
363+
if not self._closed:
364+
# Mark as closed to prevent further cleanup attempts
365+
self._closed = True
366+
367+
# Don't try to close async resources during garbage collection
368+
# This can cause "coroutine was never awaited" warnings
369+
# Proper cleanup should be done via explicit close() calls or context managers
344370

345371
async def set_caching(self) -> None:
346372
"""

src/biothings_typed_client/genes.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -181,41 +181,6 @@ def __init__(self, caching: bool = True):
181181

182182
def _response_model(self) -> type[GeneResponse]:
183183
return GeneResponse
184-
185-
async def __aenter__(self):
186-
return self
187-
188-
async def __aexit__(self, exc_type, exc_val, exc_tb):
189-
await self.close()
190-
191-
async def close(self):
192-
"""Close the client connection"""
193-
if not self._closed and hasattr(self._client, 'close'):
194-
try:
195-
await self._client.close()
196-
except Exception:
197-
# Ignore any errors during cleanup
198-
pass
199-
finally:
200-
self._closed = True
201-
202-
def __del__(self):
203-
"""Cleanup when the object is deleted"""
204-
if not self._closed and hasattr(self._client, 'close'):
205-
import asyncio
206-
try:
207-
loop = asyncio.get_event_loop()
208-
if loop.is_running():
209-
# If loop is running, create a task to close the client
210-
loop.create_task(self._client.close())
211-
else:
212-
# If loop is not running, run the close operation
213-
loop.run_until_complete(self._client.close())
214-
except Exception:
215-
# Ignore any errors during cleanup
216-
pass
217-
finally:
218-
self._closed = True
219184

220185
async def getgene(
221186
self,

src/biothings_typed_client/genesets.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -181,21 +181,13 @@ async def close(self):
181181

182182
def __del__(self):
183183
"""Cleanup when the object is deleted"""
184-
if not self._closed and hasattr(self._client, 'close'):
185-
import asyncio
186-
try:
187-
loop = asyncio.get_event_loop()
188-
if loop.is_running():
189-
# If loop is running, create a task to close the client
190-
loop.create_task(self._client.close())
191-
else:
192-
# If loop is not running, run the close operation
193-
loop.run_until_complete(self._client.close())
194-
except Exception:
195-
# Ignore any errors during cleanup
196-
pass
197-
finally:
198-
self._closed = True
184+
if not self._closed:
185+
# Mark as closed to prevent further cleanup attempts
186+
self._closed = True
187+
188+
# Don't try to close async resources during garbage collection
189+
# This can cause "coroutine was never awaited" warnings
190+
# Proper cleanup should be done via explicit close() calls or context managers
199191

200192
async def metadata(self) -> Dict[str, Any]:
201193
"""

src/biothings_typed_client/taxons.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ class TaxonResponse(BaseModel):
1010
"""Response model for taxon information"""
1111
model_config = ConfigDict(extra='allow')
1212

13-
id: str = Field(description="Taxon identifier", validation_alias="_id")
14-
version: int = Field(description="Version number of the data", validation_alias="_version")
13+
id: str = Field(description="Taxon identifier", validation_alias="_id", serialization_alias="_id")
14+
version: int = Field(description="Version number of the data", validation_alias="_version", serialization_alias="_version")
1515
authority: Optional[List[str]] = Field(default=None, description="Taxonomic authority")
1616
common_name: Optional[str] = Field(default=None, description="Common name")
1717
genbank_common_name: Optional[str] = Field(default=None, description="GenBank common name")

src/biothings_typed_client/variants.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -489,24 +489,16 @@ def __del__(self):
489489
Cleanup when the object is deleted.
490490
491491
This method ensures that resources are properly cleaned up even if the client
492-
is not explicitly closed. It attempts to close the client in a safe manner,
493-
handling both running and non-running event loops.
492+
is not explicitly closed. However, to avoid "coroutine was never awaited" warnings,
493+
async resource cleanup is not performed during garbage collection.
494494
"""
495-
if not self._closed and hasattr(self._client, 'close'):
496-
import asyncio
497-
try:
498-
loop = asyncio.get_event_loop()
499-
if loop.is_running():
500-
# If loop is running, create a task to close the client
501-
loop.create_task(self._client.close())
502-
else:
503-
# If loop is not running, run the close operation
504-
loop.run_until_complete(self._client.close())
505-
except Exception:
506-
# Ignore any errors during cleanup
507-
pass
508-
finally:
509-
self._closed = True
495+
if not self._closed:
496+
# Mark as closed to prevent further cleanup attempts
497+
self._closed = True
498+
499+
# Don't try to close async resources during garbage collection
500+
# This can cause "coroutine was never awaited" warnings
501+
# Proper cleanup should be done via explicit close() calls or context managers
510502

511503
async def getvariant(
512504
self,

test_fix.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
"""Test script to verify the async client fix"""
3+
4+
import asyncio
5+
import warnings
6+
import pytest
7+
from biothings_typed_client.genes import GeneClientAsync
8+
9+
@pytest.mark.asyncio
10+
async def test_async_client_instantiation():
11+
"""Test that creating an async client doesn't produce the original warning"""
12+
print("Creating async gene client...")
13+
14+
# Test 1: Direct instantiation with proper cleanup
15+
print("Testing direct instantiation:")
16+
async with GeneClientAsync(caching=False) as client:
17+
print("✓ Client created successfully without the set_caching warning")
18+
19+
# Test 3: Manual caching setup
20+
print("Testing manual caching setup:")
21+
await client.set_caching()
22+
print("✓ Manual caching setup completed")
23+
24+
# Test 2: Use as context manager (caching will be set up automatically)
25+
print("\nTesting context manager pattern:")
26+
async with GeneClientAsync(caching=False) as client_cm:
27+
print("✓ Context manager setup completed")
28+
29+
# Test 4: Factory method with proper cleanup
30+
print("\nTesting factory method:")
31+
client_factory = await GeneClientAsync.create(caching=False)
32+
try:
33+
print("✓ Factory method completed")
34+
finally:
35+
await client_factory.close()
36+
37+
print("\n✅ All tests passed!")
38+
39+
if __name__ == "__main__":
40+
# Capture warnings to see if we still get the original issue
41+
with warnings.catch_warnings(record=True) as w:
42+
warnings.simplefilter("always")
43+
asyncio.run(test_async_client_instantiation())
44+
45+
# Check for the specific warning we fixed
46+
set_caching_warnings = [warning for warning in w
47+
if "coroutine 'AsyncBiothingClient._set_caching' was never awaited" in str(warning.message)]
48+
49+
if set_caching_warnings:
50+
print(f"\n❌ Original warning still present: {len(set_caching_warnings)} occurrences")
51+
for warning in set_caching_warnings:
52+
print(f" {warning.message}")
53+
else:
54+
print("\n✅ Original set_caching warning is fixed!")
55+
56+
# Show any other warnings (for informational purposes)
57+
other_warnings = [warning for warning in w
58+
if "coroutine 'AsyncBiothingClient._set_caching' was never awaited" not in str(warning.message)]
59+
if other_warnings:
60+
print(f"\nℹ️ Other warnings present ({len(other_warnings)}):")
61+
for warning in other_warnings:
62+
print(f" {warning.message}")
63+
else:
64+
print("\nℹ️ No other warnings detected")

tests/test_chem_async.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
@pytest_asyncio.fixture
1010
async def async_client():
1111
"""Fixture providing an asynchronous chem client"""
12-
client = ChemClientAsync()
13-
yield client
14-
await client.close()
12+
async with ChemClientAsync() as client:
13+
yield client
1514

1615
@pytest.mark.asyncio
1716
async def test_getchem_async(async_client: ChemClientAsync):

tests/test_genes.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,35 @@ def sync_client():
1414

1515
@pytest_asyncio.fixture
1616
async def async_client():
17-
client = GeneClientAsync()
18-
yield client
19-
await client.close()
17+
async with GeneClientAsync() as client:
18+
yield client
2019

2120
def test_getgene_sync(sync_client: GeneClient):
2221
"""Test synchronous gene retrieval"""
23-
gene = sync_client.getgene("1017")
22+
gene = sync_client.getgene("672")
2423
assert gene is not None
2524
assert isinstance(gene, GeneResponse)
26-
assert gene.id == "1017"
27-
assert gene.symbol == "CDK2"
28-
assert gene.name == "cyclin dependent kinase 2"
25+
assert gene.id == "672"
26+
assert gene.symbol == "BRCA1"
27+
assert gene.name is not None
2928

3029
def test_getgene_sync_with_fields(sync_client: GeneClient):
3130
"""Test synchronous gene retrieval with specific fields"""
32-
gene = sync_client.getgene("1017", fields=["symbol", "name"])
31+
gene = sync_client.getgene("672", fields=["symbol", "name"])
3332
assert gene is not None
3433
assert isinstance(gene, GeneResponse)
35-
assert gene.id == "1017"
36-
assert gene.symbol == "CDK2"
37-
assert gene.name == "cyclin dependent kinase 2"
34+
assert gene.id == "672"
35+
assert gene.symbol == "BRCA1"
36+
assert gene.name is not None
3837
assert gene.refseq is None # Not requested
3938

4039
def test_getgenes_sync(sync_client: GeneClient):
4140
"""Test synchronous multiple gene retrieval"""
42-
genes = sync_client.getgenes(["1017", "1018"])
41+
genes = sync_client.getgenes(["672", "675"])
4342
assert len(genes) == 2
4443
assert all(isinstance(gene, GeneResponse) for gene in genes)
45-
assert genes[0].id == "1017"
46-
assert genes[1].id == "1018"
44+
assert genes[0].id == "672"
45+
assert genes[1].id == "675"
4746

4847
def test_query_sync(sync_client: GeneClient):
4948
"""Test synchronous gene query"""

tests/test_genes_async.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
@pytest_asyncio.fixture
66
async def async_client():
77
"""Fixture providing an asynchronous gene client"""
8-
client = GeneClientAsync()
9-
yield client
10-
await client.close()
8+
async with GeneClientAsync() as client:
9+
yield client
1110

1211
@pytest.mark.asyncio
1312
async def test_getgene_async(async_client: GeneClientAsync):

0 commit comments

Comments
 (0)