diff --git a/.gitignore b/.gitignore index f24f345..8609005 100644 --- a/.gitignore +++ b/.gitignore @@ -55,15 +55,41 @@ ENV/ env.bak/ venv.bak/ +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo + +# macOS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp +*~ + +# Logs +*.log + +# API keys and secrets (just in case) +.env.local +.env.production +config/secrets.json +secrets.json + # OpenAPI files -openapi_new.yml openapi_old.yml +openapi_new.yml -# SDK update instructions +#internal files SDK_UPDATE_INSTRUCTIONS.md -# IDE specific files -.idea/ -.vscode/ -*.swp -*.swo \ No newline at end of file +# Note: OpenAPI files and SDK instructions are kept in the repository +# for documentation and development reference purposes \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index df25883..33478ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ All notable changes to the DexPaprika SDK for Python will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - 2025-01-27 + +### Breaking Changes +- **DEPRECATED**: Global pools method `pools.list()` due to DexPaprika API v1.3.0 changes +- **MIGRATION REQUIRED**: The global `/pools` endpoint now returns `410 Gone` +- All pool operations now require network specification for better performance + +### Added +- Automatic fallback for deprecated `pools.list()` method to Ethereum network +- New `reorder` parameter in `tokens.get_pools()` method for reordering pool metrics +- Comprehensive deprecation warnings with migration guidance +- Enhanced error handling for `410 Gone` responses + +### Changed +- Updated SDK version to 0.3.0 to reflect API compatibility with DexPaprika v1.3.0 +- Improved documentation with migration examples +- Updated user agent string to match new SDK version + +### Migration Guide +```python +# Before (deprecated): +pools = client.pools.list() + +# After (recommended): +pools = client.pools.list_by_network('ethereum') +pools = client.pools.list_by_network('solana') +pools = client.pools.list_by_network('fantom') + +# Token pools with reordering (new feature): +pools = client.tokens.get_pools( + network_id="ethereum", + token_address="0x...", + reorder=True # Makes the specified token primary for all metrics +) +``` + ## [0.2.0] - 2024-07-01 ### Added diff --git a/README.md b/README.md index 2a1c7aa..4fec555 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,54 @@ A Python client for the DexPaprika API. This SDK provides easy access to real-ti pip install dexpaprika-sdk # Or install from source -git clone https://github.com/donbagger/dexpaprika-sdk-python.git +git clone https://github.com/coinpaprika/dexpaprika-sdk-python.git cd dexpaprika-sdk-python pip install -e . ``` +## Migration Guide (v0.3.0) + +**Important:** Version 0.3.0 includes breaking changes due to DexPaprika API v1.3.0 updates. + +### Global Pools Endpoint Deprecation + +The global `/pools` endpoint has been removed. If you were using `client.pools.list()`, you need to update your code: + +**Before (deprecated):** +```python +# This method is deprecated and will show warnings +pools = client.pools.list(limit=10) +``` + +**After (recommended):** +```python +# Use network-specific methods instead +eth_pools = client.pools.list_by_network("ethereum", limit=10) +solana_pools = client.pools.list_by_network("solana", limit=10) +``` + +### New Token Pools Features + +The `tokens.get_pools()` method now supports a new `reorder` parameter: + +```python +# Reorder pools so the specified token becomes primary for all metrics +pools = client.tokens.get_pools( + network_id="ethereum", + token_address="0xa0b86a33e6441b8466395bf92e8aa0cb53ad20aa", # USDC + reorder=True # Makes USDC the primary token for calculations +) +``` + +### Backward Compatibility + +For backward compatibility, the deprecated `pools.list()` method will: +- Show deprecation warnings +- Automatically fall back to Ethereum network +- Continue working until a future version + +We strongly recommend updating your code to use network-specific methods for better performance and future compatibility. + ## Usage ### Basic Example @@ -49,8 +92,13 @@ for network in networks: stats = client.utils.get_stats() print(f"DexPaprika stats: {stats.chains} chains, {stats.pools} pools") -# Get top pools by volume -pools = client.pools.list(limit=5, order_by="volume_usd", sort="desc") +# Get top pools by volume (network-specific) +pools = client.pools.list_by_network( + network_id="ethereum", + limit=5, + order_by="volume_usd", + sort="desc" +) for pool in pools.pools: token_pair = f"{pool.tokens[0].symbol}/{pool.tokens[1].symbol}" if len(pool.tokens) >= 2 else "Unknown Pair" print(f"- {token_pair} on {pool.dex_name} ({pool.chain}): ${pool.volume_usd:.2f} volume") diff --git a/dexpaprika_sdk/__init__.py b/dexpaprika_sdk/__init__.py index 6250d5a..83ae4a2 100644 --- a/dexpaprika_sdk/__init__.py +++ b/dexpaprika_sdk/__init__.py @@ -21,7 +21,7 @@ Stats ) -__version__ = "0.2.0" +__version__ = "0.3.0" __all__ = [ "DexPaprikaClient", # Models diff --git a/dexpaprika_sdk/api/pools.py b/dexpaprika_sdk/api/pools.py index 5b2e4db..dcadb9f 100644 --- a/dexpaprika_sdk/api/pools.py +++ b/dexpaprika_sdk/api/pools.py @@ -1,4 +1,5 @@ from typing import List, Optional, Dict, Any, Set +import warnings from .base import BaseAPI from ..models.pools import ( @@ -22,7 +23,12 @@ def list( order_by: str = "volume_usd" ) -> PoolsResponse: """ - Get a list of top pools across all networks. + DEPRECATED: Get a list of top pools across all networks. + + This method is deprecated due to API changes. The global /pools endpoint + has been removed. Use list_by_network(network_id, ...) instead. + + For backward compatibility, this method now defaults to Ethereum network. Args: page: Page number for pagination @@ -35,21 +41,53 @@ def list( Raises: ValueError: If any parameter is invalid + + Migration Examples: + # Before (deprecated): + pools = client.pools.list() + + # After (recommended): + pools = client.pools.list_by_network('ethereum') + pools = client.pools.list_by_network('solana') """ + # Issue deprecation warning + warnings.warn( + "The pools.list() method is deprecated. The global /pools endpoint has been " + "removed in API v1.3.0. Use pools.list_by_network(network_id) instead. " + "This method now defaults to Ethereum network for backward compatibility. " + "Examples: client.pools.list_by_network('ethereum'), " + "client.pools.list_by_network('solana')", + DeprecationWarning, + stacklevel=2 + ) + # Validate parameters self._validate_range("page", page, min_val=0) self._validate_range("limit", limit, min_val=1, max_val=100) self._validate_enum("sort", sort, self.VALID_SORT_VALUES) self._validate_enum("order_by", order_by, self.VALID_ORDER_BY_VALUES) - # Get top pools - params = {"page": page, "limit": limit, "sort": sort, "order_by": order_by} - data = self._get("/pools", params=params) - - # ensure pools exists - if 'pools' not in data: data['pools'] = [] + try: + # Attempt to call the deprecated endpoint first for debugging/testing + params = {"page": page, "limit": limit, "sort": sort, "order_by": order_by} + data = self._get("/pools", params=params) - return PoolsResponse(**data) + # ensure pools exists + if 'pools' not in data: data['pools'] = [] + + return PoolsResponse(**data) + + except Exception as e: + # If we get a 410 Gone or any other error, fall back to Ethereum + # Check if it's a 410 Gone status specifically + if hasattr(e, 'response') and hasattr(e.response, 'status_code') and e.response.status_code == 410: + # Provide a more specific error message for 410 Gone + print("WARNING: The global /pools endpoint has been permanently removed (410 Gone). " + "Falling back to Ethereum network. Please update your code to use " + "pools.list_by_network(network_id) instead.") + + # Fall back to Ethereum network for backward compatibility + return self.list_by_network("ethereum", page=page, limit=limit, sort=sort, order_by=order_by) def list_by_network( self, diff --git a/dexpaprika_sdk/api/tokens.py b/dexpaprika_sdk/api/tokens.py index 7e1103a..1e405de 100644 --- a/dexpaprika_sdk/api/tokens.py +++ b/dexpaprika_sdk/api/tokens.py @@ -45,6 +45,7 @@ def get_pools( sort: str = "desc", order_by: str = "volume_usd", address: Optional[str] = None, + reorder: Optional[bool] = None, ) -> PoolsResponse: """ Get a list of top liquidity pools for a specific token on a network. @@ -58,6 +59,9 @@ def get_pools( order_by: Field to order by ("volume_usd", "price_usd", "transactions", "last_price_change_usd_24h", "created_at") address: Filter pools that contain this additional token address + reorder: If true, reorders the pool so that the specified token becomes + the primary token for all metrics and calculations. Useful when + the provided token is not the first token in the pool. Returns: Response containing a list of pools for the given token @@ -79,6 +83,7 @@ def get_pools( "sort": sort, "order_by": order_by, "address": address, + "reorder": reorder, } params = self._clean_params(params) diff --git a/dexpaprika_sdk/client.py b/dexpaprika_sdk/client.py index cdb01c9..ca40c6f 100644 --- a/dexpaprika_sdk/client.py +++ b/dexpaprika_sdk/client.py @@ -19,7 +19,7 @@ def __init__( self, base_url: str = "https://api.dexpaprika.com", session: Optional[requests.Session] = None, - user_agent: str = "DexPaprika-SDK-Python/0.1.0", + user_agent: str = "DexPaprika-SDK-Python/0.3.0", max_retries: int = 4, backoff_times: List[float] = None, ): diff --git a/tests/test_all_endpoints.py b/tests/test_all_endpoints.py index 67dfbf5..8274c43 100644 --- a/tests/test_all_endpoints.py +++ b/tests/test_all_endpoints.py @@ -49,16 +49,25 @@ def test_utils_get_stats(client): assert hasattr(stats, 'tokens') # Pools API tests -def test_pools_list(client): - pools_response = client.pools.list(limit=5) - assert pools_response is not None - assert hasattr(pools_response, 'pools') - assert len(pools_response.pools) > 0 - -def test_pools_list_by_network(client, test_data): +def test_pools_list_deprecated(client): + """Test the deprecated global pools method with deprecation warning.""" + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + pools_response = client.pools.list(limit=5) + assert pools_response is not None + assert hasattr(pools_response, 'pools') + assert len(pools_response.pools) > 0 + # Check that a deprecation warning was issued + assert len(w) > 0 + assert any("deprecated" in str(warning.message).lower() for warning in w) + +def test_pools_list_by_network_primary(client, test_data): + """Test the primary pools list method using network-specific endpoint.""" pools_response = client.pools.list_by_network(test_data["ethereum_network"], limit=5) assert pools_response is not None assert hasattr(pools_response, 'pools') + assert len(pools_response.pools) > 0 def test_dexes_list(client, test_data): dexes_response = client.dexes.list(test_data["ethereum_network"]) @@ -121,6 +130,17 @@ def test_tokens_get_pools(client, test_data): assert token_pools is not None assert hasattr(token_pools, 'pools') +def test_tokens_get_pools_with_reorder(client, test_data): + """Test the tokens.get_pools method with the new reorder parameter.""" + token_pools = client.tokens.get_pools( + test_data["test_pool_network"], + test_data["test_token_address"], + limit=5, + reorder=True + ) + assert token_pools is not None + assert hasattr(token_pools, 'pools') + # Search API tests def test_search_search(client): search_results = client.search.search("Jockey") @@ -149,8 +169,8 @@ def test_search_search(client): test_networks_list, test_networks_list_dexes, test_utils_get_stats, - test_pools_list, - test_pools_list_by_network, + test_pools_list_deprecated, + test_pools_list_by_network_primary, test_dexes_list, test_pools_list_by_dex, test_pools_get_details, @@ -158,6 +178,7 @@ def test_search_search(client): test_pools_get_transactions, test_tokens_get_details, test_tokens_get_pools, + test_tokens_get_pools_with_reorder, test_search_search ] @@ -167,7 +188,7 @@ def test_search_search(client): for test_func in test_functions: print(f"Testing {test_func.__name__}...") try: - if test_func.__name__ == "test_networks_list" or test_func.__name__ == "test_utils_get_stats" or test_func.__name__ == "test_pools_list" or test_func.__name__ == "test_search_search": + if test_func.__name__ in ["test_networks_list", "test_utils_get_stats", "test_pools_list_deprecated", "test_search_search"]: test_func(client) else: test_func(client, test_data) diff --git a/tests/test_validation.py b/tests/test_validation.py index 156ee51..570b920 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -34,12 +34,12 @@ def test_enum_validation(self): """Test validation of enum parameters.""" # Test invalid sort order with self.assertRaises(ValueError) as context: - self.client.pools.list(sort="invalid") + self.client.pools.list_by_network("ethereum", sort="invalid") self.assertIn("sort must be one of: asc, desc", str(context.exception)) # Test invalid order_by with self.assertRaises(ValueError) as context: - self.client.pools.list(order_by="invalid") + self.client.pools.list_by_network("ethereum", order_by="invalid") self.assertIn("order_by must be one of:", str(context.exception)) # Test invalid interval @@ -56,17 +56,17 @@ def test_range_validation(self): """Test validation of numeric range parameters.""" # Test negative page with self.assertRaises(ValueError) as context: - self.client.pools.list(page=-1) + self.client.pools.list_by_network("ethereum", page=-1) self.assertIn("page must be at least 0", str(context.exception)) # Test invalid limit (too low) with self.assertRaises(ValueError) as context: - self.client.pools.list(limit=0) + self.client.pools.list_by_network("ethereum", limit=0) self.assertIn("limit must be at least 1", str(context.exception)) # Test invalid limit (too high) with self.assertRaises(ValueError) as context: - self.client.pools.list(limit=101) + self.client.pools.list_by_network("ethereum", limit=101) self.assertIn("limit must be at most 100", str(context.exception)) if __name__ == "__main__":