├── .gitignore ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── .readthedocs.yaml ├── Makefile ├── conf.py ├── contributing.md ├── history.md ├── index.md ├── readme.md ├── requirements.txt └── usage-examples.md ├── opensea ├── __init__.py ├── opensea.py ├── opensea_api.py └── utils.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests └── __init__.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | 4 | # Distribution / packaging 5 | .Python 6 | build/ 7 | develop-eggs/ 8 | dist/ 9 | downloads/ 10 | eggs/ 11 | .eggs/ 12 | lib/ 13 | lib64/ 14 | parts/ 15 | sdist/ 16 | var/ 17 | wheels/ 18 | pip-wheel-metadata/ 19 | share/python-wheels/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | MANIFEST 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Sphinx documentation 30 | docs/_build/ 31 | 32 | # Environments 33 | .env 34 | .venv 35 | env/ 36 | venv/ 37 | ENV/ 38 | 39 | # pytest 40 | .pytest_cache 41 | 42 | 43 | .pypirc 44 | .vscode/ 45 | .tox/ 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * [Submit an issue](https://github.com/zseta/python-opensea/issues) to report a bug or suggest a new feature 4 | * Submit a PR with a bugfix or a new feature. 5 | * Write a blog post or a tutorial about how you are using the OpenSea API and this wrapper -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## 0.1.8 (2022-xx-xx) 4 | * Add `include_orders` in the assets api 5 | * Add cursor-based pagination in `assets` endpoint 6 | 7 | ## 0.1.7 (2022-03-26) 8 | * Add support for [asset listings](https://docs.opensea.io/reference/asset-listings) 9 | and [asset offers](https://docs.opensea.io/reference/asset-offers) endpoints 10 | * Add `occured_after` and `collection_editor` arguments to events endpoint 11 | * Handle SSL error when making requests 12 | * Docs: add example to paginate the events endpoint (using `events_backfill()`) 13 | 14 | ## 0.1.6 (2022-02-25) 15 | * Fix /events endpoint pagination (`events_backfill()` function) by 16 | passing only *the cursor hash* and not the full URL to the next request. 17 | 18 | ## 0.1.5 (2022-02-17) 19 | 20 | * Ability to override base_url with any other URL 21 | * Support for cursor-based pagination for /events endpoint (and removed deprecated arguments) 22 | * New function to help paginate the /events endpoint 23 | * Introducing a temporary function to fix the `next` url problem until OpenSea addresses this issue 24 | * Minor docs updates and cleanup 25 | 26 | 27 | ## 0.1.3 (2021-12-03) 28 | 29 | * Ability to reach all endpoints from one `OpenseaAPI` object 30 | * API key support (Opensea requires it from now on) 31 | 32 | ## 0.1.0 (2021-11-07) 33 | 34 | * First release on PyPI. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Attila Tóth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.md 2 | include HISTORY.md 3 | include LICENSE 4 | include README.md 5 | 6 | recursive-exclude * __pycache__ 7 | recursive-exclude * *.py[co] 8 | recursive-exclude tests * 9 | 10 | recursive-include docs *.md conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint/flake8: ## check style with flake8 51 | flake8 opensea tests 52 | lint/black: ## check style with black 53 | black --check opensea tests 54 | 55 | lint: lint/flake8 lint/black ## check style 56 | 57 | test: ## run tests quickly with the default Python 58 | pytest 59 | 60 | test-all: ## run tests on every Python version with tox 61 | tox 62 | 63 | coverage: ## check code coverage quickly with the default Python 64 | coverage run --source opensea -m pytest 65 | coverage report -m 66 | coverage html 67 | $(BROWSER) htmlcov/index.html 68 | 69 | docs: ## generate Sphinx HTML documentation, including API docs 70 | rm -f docs/opensea.md 71 | rm -f docs/modules.md 72 | sphinx-apidoc -o docs/ opensea 73 | $(MAKE) -C docs clean 74 | $(MAKE) -C docs html 75 | $(BROWSER) docs/_build/html/index.html 76 | 77 | servedocs: docs ## compile the docs watching for changes 78 | watchmedo shell-command -p '*.md' -c '$(MAKE) -C docs html' -R -D . 79 | 80 | release: dist ## package and upload a release 81 | twine upload dist/* 82 | 83 | dist: clean ## builds source and wheel package 84 | python setup.py sdist 85 | python setup.py bdist_wheel 86 | ls -l dist 87 | 88 | install: clean ## install the package to the active Python's site-packages 89 | python setup.py install 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSea NFT API Python 3 wrapper 2 | This an API wrapper library for the [OpenSea API](https://docs.opensea.io/reference/api-overview) written in Python 3. 3 | 4 | The library provides a simplified interface to fetch a diverse set of NFT data points from OpenSea. 5 | 6 | ## Supported endpoints 7 | The wrapper covers the following OpenSea API endpoints: 8 | 9 | * Single asset ([/asset](https://docs.opensea.io/reference/retrieving-a-single-asset)) 10 | * Single asset contract ([/asset_contract](https://docs.opensea.io/reference/retrieving-a-single-contract)) 11 | * Single collection ([/collection](https://docs.opensea.io/reference/retrieving-a-single-collection)) 12 | * Collection stats ([/collection/{slug}/stats](https://docs.opensea.io/reference/retrieving-collection-stats)) 13 | * Multiple assets] ([/assets](https://docs.opensea.io/reference/getting-assets)) 14 | * Multiple collections ([/collections](https://docs.opensea.io/reference/retrieving-collections)) 15 | * Multiple events ([/events](https://docs.opensea.io/reference/retrieving-asset-events)) 16 | * Multiple bundles ([/bundles](https://docs.opensea.io/reference/retrieving-bundles)) 17 | * Asset listings ([/asset/{asset_contract_address}/{token_id}/listings](https://docs.opensea.io/reference/asset-listings)) 18 | * Asset offers ([/asset/{asset_contract_address}/{token_id}/offers](https://docs.opensea.io/reference/asset-offers)) 19 | 20 | 21 | ## Prerequisite 22 | You need to have an **API key** to use the OpenSea API, and thus 23 | you need one to use this wrapper too. [You can request a key here.](https://docs.opensea.io/reference/request-an-api-key)

24 | NOTE: The API key can take over 4-7 days to be delivered. It also requires you to show the project you are working on. 25 | 26 | ## Installation 27 | Install with pip: 28 | ```bash 29 | virtualenv env && source env/bin/activate 30 | pip install opensea-api 31 | ``` 32 | 33 | ### Upgrade 34 | ```bash 35 | pip install opensea-api -U 36 | ``` 37 | 38 | ## Usage examples 39 | 40 | ```python 41 | # import the OpenseaAPI object from the opensea module 42 | from opensea import OpenseaAPI 43 | 44 | # create an object to interact with the Opensea API (need an api key) 45 | api = OpenseaAPI(apikey="YOUR APIKEY") 46 | 47 | # fetch a single asset 48 | contract_address = "0x495f947276749Ce646f68AC8c248420045cb7b5e" 49 | token_id = "66406747123743156841746366950152533278033835913591691491127082341586364792833" 50 | result = api.asset(asset_contract_address=contract_address, token_id=token_id) 51 | 52 | # fetch multiple assets 53 | result = api.assets(owner="0xce90a7949bb78892f159f428d0dc23a8e3584d75", limit=3) 54 | 55 | # fetch a single contract 56 | result = api.contract(asset_contract_address="0x495f947276749Ce646f68AC8c248420045cb7b5e") 57 | 58 | # fetch a single collection 59 | result = api.collection(collection_slug="cryptopunks") 60 | 61 | # fetch multiple collections 62 | result = api.collections(asset_owner="0xce90a7949bb78892f159f428d0dc23a8e3584d75", limit=3) 63 | 64 | # fetch collection stats 65 | result = api.collection_stats(collection_slug="cryptopunks") 66 | 67 | # fetch multiple events 68 | from opensea import utils as opensea_utils 69 | 70 | period_end = opensea_utils.datetime_utc(2021, 11, 6, 14, 30) 71 | result = api.events( 72 | occurred_before=period_end, 73 | limit=10, 74 | export_file_name="events.json", 75 | ) 76 | 77 | # fetch multiple bundles 78 | result = api.bundles(limit=2) 79 | 80 | # paginate the events endpoint (cursors are handled internally) 81 | from datetime import datetime, timezone 82 | 83 | start_at = datetime(2021, 10, 5, 3, 25, tzinfo=timezone.utc) 84 | finish_at = datetime(2021, 10, 5, 3, 20, tzinfo=timezone.utc) 85 | 86 | event_generator = api.events_backfill(start=start_at, 87 | until=finish_at, 88 | event_type="successful") 89 | for event in event_generator: 90 | if event is not None: 91 | print(event) # or do other things with the event data 92 | ``` 93 | 94 | [Here's a demo video showcasing the basics.](https://www.youtube.com/watch?v=ga4hTqNRjfw) 95 | 96 | ## Documentation 97 | * [Wrapper documentation](https://opensea-api.attilatoth.dev) 98 | * [OpenSea API documentation](https://docs.opensea.io/reference/api-overview) 99 | 100 | -------------------------------------------------------------------------------- /docs/.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF 13 | formats: all 14 | 15 | # Optionally set the version of Python and requirements required to build your docs 16 | python: 17 | version: "3.8" 18 | install: 19 | - requirements: docs/requirements.txt 20 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = opensea 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # opensea documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import opensea 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'myst_parser'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = ['.md'] 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'OpenSea API Python wrapper' 50 | copyright = "2021, Attila Tóth" 51 | author = "Attila Tóth" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = opensea.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = opensea.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'sphinx_rtd_theme' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'openseadoc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'opensea.tex', 131 | 'OpenSea API Python wrapper documentation', 132 | 'Attila Toth', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'opensea', 142 | 'OpenSea API Python wrapper documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'opensea', 154 | 'OpenSea API Python wrapper documentation', 155 | author, 156 | 'opensea', 157 | 'Python 3 wrapper for the OpenSea API', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CONTRIBUTING.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/history.md: -------------------------------------------------------------------------------- 1 | ```{include} ../HISTORY.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # OpenSea API Python wrapper 2 | 3 | ```{toctree} 4 | :maxdepth: 2 5 | readme 6 | usage-examples 7 | contributing 8 | history 9 | ``` 10 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ```{include} ../README.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | myst-parser==0.15.2 2 | sphinx-rtd-theme==1.0.0 -------------------------------------------------------------------------------- /docs/usage-examples.md: -------------------------------------------------------------------------------- 1 | # Usage examples 2 | 3 | ## Get data about a single asset 4 | ```python 5 | from opensea import OpenseaAPI 6 | 7 | api = OpenseaAPI(apikey="") 8 | result = api.asset(asset_contract_address="0x495f947276749Ce646f68AC8c248420045cb7b5e", 9 | token_id="66406747123743156841746366950152533278033835913591691491127082341586364792833") 10 | print(result) 11 | ``` 12 | 13 | ## Get data about a single asset contract 14 | ```python 15 | from opensea import OpenseaAPI 16 | 17 | api = OpenseaAPI(apikey="") 18 | result = api.contract(asset_contract_address="0x495f947276749Ce646f68AC8c248420045cb7b5e") 19 | print(result) 20 | ``` 21 | 22 | ## Get data about a single collection 23 | ```python 24 | from opensea import OpenseaAPI 25 | 26 | api = OpenseaAPI(apikey="") 27 | result = api.collection(collection_slug="cryptopunks") 28 | print(result) 29 | ``` 30 | 31 | ## Get collection stats 32 | ```python 33 | from opensea import OpenseaAPI 34 | 35 | api = OpenseaAPI(apikey="") 36 | result = api.collection_stats(collection_slug="cryptopunks") 37 | print(result) 38 | ``` 39 | 40 | ## Get data about multiple assets 41 | This example fetches three NFTs that Snoop Dogg owns: 42 | ```python 43 | from opensea import OpenseaAPI 44 | 45 | api = OpenseaAPI(apikey="") 46 | result = api.assets(owner="0xce90a7949bb78892f159f428d0dc23a8e3584d75", 47 | limit=3) 48 | print(result) 49 | ``` 50 | 51 | ## Get data about multiple collections 52 | This example creates a JSON file with 3 collections where Snoop Dogg is an owner: 53 | ```python 54 | from opensea import OpenseaAPI 55 | 56 | api = OpenseaAPI(apikey="") 57 | result = api.collections(asset_owner="0xce90a7949bb78892f159f428d0dc23a8e3584d75", 58 | limit=3, 59 | export_file_name='snoop_collections.json') 60 | print(result) 61 | ``` 62 | 63 | ## Get data about multiple events 64 | This example creates a JSON file with 10 events that happened between the 65 | defined time period (UTC timezone) between `2021-11-06 14:25` and `2021-11-06 14:30` 66 | ```python 67 | from opensea import OpenseaAPI 68 | from opensea import utils 69 | 70 | api = OpenseaAPI(apikey="") 71 | period_start = utils.datetime_utc(2021, 11, 6, 14, 25) 72 | period_end = utils.datetime_utc(2021, 11, 6, 14, 30) 73 | result = api.events(occurred_after=period_start, 74 | occurred_before=period_end, 75 | limit=10, 76 | export_file_name='events.json') 77 | print(result) 78 | ``` 79 | 80 | ## Get data about multiple bundles 81 | ```python 82 | from opensea import OpenseaAPI 83 | 84 | api = OpenseaAPI(apikey="") 85 | result = api.bundles(limit=3) 86 | print(result) 87 | ``` -------------------------------------------------------------------------------- /opensea/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for OpenSea API Python wrapper.""" 2 | 3 | __author__ = """Attila Toth""" 4 | __email__ = "hello@attilatoth.dev" 5 | __version__ = '0.1.7' 6 | __all__ = ["Events", "Asset", "Assets", "Contract", "Collection", 7 | "CollectionStats", "Collections", "Bundles", "utils", 8 | "OpenseaAPI"] 9 | 10 | from opensea.opensea import Events, Asset, Assets, Contract, Collection, \ 11 | CollectionStats, Collections, Bundles 12 | from opensea.opensea_api import OpenseaAPI 13 | from opensea import utils 14 | -------------------------------------------------------------------------------- /opensea/opensea.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from datetime import datetime 3 | from opensea import utils 4 | 5 | 6 | class OpenseaBase: 7 | 8 | def __init__(self, endpoint, version="v1", 9 | base_url="https://api.opensea.io/api"): 10 | """Base class to interact with the OpenSea API and fetch NFT data. 11 | 12 | Args: 13 | endpoint (str): OpenSea API endpoint, eg. 'asset' or 'collections' 14 | version (str, optional): API version. Defaults to "v1" 15 | base 16 | """ 17 | self.api_url = f"{base_url}/{version}/{endpoint}" 18 | 19 | def _make_request(self, params=None, export_file_name="", 20 | return_response=False): 21 | """Makes a request to the OpenSea API and returns either a response 22 | object or dictionary. 23 | 24 | Args: 25 | params (dict, optional): Query parameters to include in the 26 | request. Defaults to None. 27 | export_file_name (str, optional): In case you want to download the 28 | data into a file, 29 | specify the filename here. Eg. 'export.json'. Be default, no file 30 | is created. 31 | return_response (bool, optional): Set it True if you want it to 32 | return the actual response object. 33 | By default, it's False, which means a dictionary will be returned. 34 | 35 | Raises: 36 | ValueError: returns the error message from the API in case one 37 | (or more) of your request parameters are incorrect. 38 | 39 | Returns: 40 | Data sent back from the API. Either a response or dict object 41 | depending on the *return_response* argument. 42 | """ 43 | response = requests.get(self.api_url, params=params) 44 | if response.status_code == 400: 45 | raise ValueError(response.text) 46 | elif response.status_code == 504: 47 | raise TimeoutError("The server reported a gateway time-out error.") 48 | if export_file_name != "": 49 | utils.export_file(response.content, export_file_name) 50 | if return_response: 51 | return response 52 | return response.json() 53 | 54 | 55 | class Events(OpenseaBase): 56 | 57 | MAX_API_ITEMS = 300 58 | 59 | def __init__(self): 60 | """Endpoint to fetch data about multiple events. 61 | More info about this endpoint in the OpenSea docs: 62 | https://docs.opensea.io/reference/retrieving-asset-events 63 | """ 64 | super().__init__(endpoint="events") 65 | 66 | def fetch( 67 | self, 68 | asset_contract_address=None, 69 | collection_slug=None, 70 | token_id=None, 71 | account_address=None, 72 | event_type=None, 73 | only_opensea=False, 74 | auction_type=None, 75 | offset=0, 76 | limit=None, 77 | occurred_before=None, 78 | occurred_after=None, 79 | export_file_name="", 80 | ): 81 | """Fetches Events data from the API. Function arguments will be passed 82 | as API query parameters. 83 | 84 | OpenSea API Events query parameters: 85 | https://docs.opensea.io/reference/retrieving-asset-events 86 | 87 | All arguments will be passed without modification, except 88 | *occurred_before* and *occurred_after*. For these two args, you need 89 | to use datetime objects when calling this function. 90 | 91 | There's one extra optional argument: 92 | export_file_name (str, optional): Exports the JSON data into a the 93 | specified file. 94 | 95 | Returns: 96 | [dict]: Events data 97 | """ 98 | if (occurred_after is not None 99 | and not isinstance(occurred_after, datetime)): 100 | raise ValueError("occurred_after must be a datetime object") 101 | 102 | if (occurred_before is not None 103 | and not isinstance(occurred_before, datetime)): 104 | raise ValueError("occurred_before must be a datetime object") 105 | 106 | query_params = { 107 | "asset_contract_address": asset_contract_address, 108 | "collection_slug": collection_slug, 109 | "token_id": token_id, 110 | "account_address": account_address, 111 | "event_type": event_type, 112 | "only_opensea": only_opensea, 113 | "auction_type": auction_type, 114 | "offset": offset, 115 | "limit": self.MAX_API_ITEMS if limit is None else limit, 116 | } 117 | if occurred_before is not None: 118 | query_params["occurred_before"] = occurred_before.timestamp() 119 | if occurred_after is not None: 120 | query_params["occurred_after"] = occurred_after.timestamp() 121 | return super()._make_request(query_params, export_file_name) 122 | 123 | 124 | class Asset(OpenseaBase): 125 | def __init__(self, asset_contract_address, token_id): 126 | """Endpoint to fetch data about a single asset. 127 | More info about this endpoint in the OpenSea docs: 128 | https://docs.opensea.io/reference/retrieving-a-single-asset 129 | 130 | Args: 131 | asset_contract_address (str) 132 | token_id (str): 133 | """ 134 | super().__init__(endpoint=f"asset/{asset_contract_address}/{token_id}") 135 | 136 | def fetch(self, account_address=None, export_file_name=""): 137 | """Fetches Asset data from the API. 138 | 139 | Args: 140 | account_address (str, optional). Defaults to None. 141 | export_file_name (str, optional): Exports the JSON data into a the 142 | specified file. 143 | 144 | Returns: 145 | [dict]: Single asset data 146 | """ 147 | query_params = {"account_address": account_address} 148 | return super()._make_request(query_params, export_file_name) 149 | 150 | 151 | class Assets(OpenseaBase): 152 | 153 | MAX_API_ITEMS = 50 154 | 155 | def __init__(self): 156 | """Endpoint to fetch data about multiple assets. 157 | More info about this endpoint in the OpenSea docs: 158 | https://docs.opensea.io/reference/getting-assets 159 | """ 160 | super().__init__(endpoint="assets") 161 | 162 | def fetch( 163 | self, 164 | owner=None, 165 | token_ids=[], 166 | asset_contract_address=None, 167 | asset_contract_addresses=None, 168 | order_by=None, 169 | order_direction=None, 170 | offset=None, 171 | limit=None, 172 | collection=None, 173 | export_file_name="", 174 | ): 175 | """Fetches assets data from the API. Function arguments will be passed 176 | as API query parameters, without modification. 177 | 178 | OpenSea API Assets query parameters: 179 | https://docs.opensea.io/reference/getting-assets 180 | 181 | There's one extra optional argument: 182 | export_file_name (str, optional): Exports the JSON data into a the 183 | specified file. 184 | 185 | Returns: 186 | [dict]: Assets data 187 | """ 188 | query_params = { 189 | "owner": owner, 190 | "token_ids": token_ids, 191 | "asset_contract_address": asset_contract_address, 192 | "asset_contract_addresses": asset_contract_addresses, 193 | "order_by": order_by, 194 | "order_direction": order_direction, 195 | "offset": offset, 196 | "limit": self.MAX_API_ITEMS if limit is None else limit, 197 | "collection": collection, 198 | } 199 | return super()._make_request(query_params, export_file_name) 200 | 201 | 202 | class Contract(OpenseaBase): 203 | def __init__(self, asset_contract_address): 204 | """Endpoint to fetch data about a single asset contract. 205 | More info about this endpoint in the OpenSea docs: 206 | https://docs.opensea.io/reference/retrieving-a-single-contract 207 | 208 | Args: 209 | asset_contract_address (str) 210 | """ 211 | super().__init__(endpoint=f"asset_contract/{asset_contract_address}") 212 | 213 | def fetch(self, export_file_name=""): 214 | """Fetches asset contract data from the API. 215 | 216 | OpenSea API Asset Contract query parameters: 217 | https://docs.opensea.io/reference/retrieving-a-single-contract 218 | 219 | Args: 220 | export_file_name (str, optional): Exports the JSON data into a the 221 | specified file. 222 | 223 | Returns: 224 | [dict]: Single asset contract data 225 | """ 226 | return super()._make_request(None, export_file_name) 227 | 228 | 229 | class Collection(OpenseaBase): 230 | def __init__(self, collection_slug): 231 | """Endpoint to fetch data about a single asset contract. 232 | More info about this endpoint in the OpenSea docs: 233 | https://docs.opensea.io/reference/retrieving-a-single-collection 234 | 235 | Args: 236 | collection_slug (str) 237 | """ 238 | super().__init__(endpoint=f"collection/{collection_slug}") 239 | 240 | def fetch(self, export_file_name=""): 241 | """Fetches collection data from the API. 242 | 243 | OpenSea API Collection query parameters: 244 | https://docs.opensea.io/reference/retrieving-a-single-collection 245 | 246 | Args: 247 | export_file_name (str, optional): Exports the JSON data into a the 248 | specified file. 249 | 250 | Returns: 251 | [dict]: Single collection data 252 | """ 253 | return super()._make_request(None, export_file_name) 254 | 255 | 256 | class CollectionStats(OpenseaBase): 257 | def __init__(self, collection_slug): 258 | """Endpoint to fetch a single collection's stats. 259 | More info about this endpoint in the OpenSea docs: 260 | https://docs.opensea.io/reference/retrieving-collection-stats 261 | 262 | Args: 263 | collection_slug (str) 264 | """ 265 | super().__init__(endpoint=f"collection/{collection_slug}/stats") 266 | 267 | def fetch(self, export_file_name=""): 268 | """Fetches collection stats data from the API. 269 | 270 | OpenSea API Collection Stats query parameters: 271 | https://docs.opensea.io/reference/retrieving-collection-stats 272 | 273 | Args: 274 | export_file_name (str, optional): Exports the JSON data into the 275 | specified file. 276 | 277 | Returns: 278 | [dict]: Collection stats 279 | """ 280 | return super()._make_request(None, export_file_name) 281 | 282 | 283 | class Collections(OpenseaBase): 284 | 285 | MAX_API_ITEMS = 300 286 | 287 | def __init__(self): 288 | """Endpoint to fetch data about multiple collections. 289 | More info about this endpoint in the OpenSea docs: 290 | https://docs.opensea.io/reference/retrieving-collections 291 | """ 292 | super().__init__(endpoint="collections") 293 | 294 | def fetch(self, asset_owner=None, offset=None, limit=None, 295 | export_file_name=""): 296 | """Fetches Collections data from the API. Function arguments will be 297 | passed as API query parameters, 298 | without modification. 299 | 300 | OpenSea API Collections query parameters: 301 | https://docs.opensea.io/reference/retrieving-collections 302 | 303 | There's one extra optional argument: 304 | export_file_name (str, optional): Exports the JSON data into a the 305 | specified file. 306 | 307 | Returns: 308 | [dict]: Collections data 309 | """ 310 | query_params = { 311 | "asset_owner": asset_owner, 312 | "offset": offset, 313 | "limit": self.MAX_API_ITEMS if limit is None else limit, 314 | } 315 | return super()._make_request(query_params, export_file_name) 316 | 317 | 318 | class Bundles(OpenseaBase): 319 | 320 | MAX_API_ITEMS = 50 321 | 322 | def __init__(self): 323 | super().__init__(endpoint="bundles") 324 | 325 | def fetch( 326 | self, 327 | on_sale=None, 328 | owner=None, 329 | asset_contract_address=None, 330 | asset_contract_addresses=[], 331 | token_ids=[], 332 | limit=None, 333 | export_file_name="", 334 | offset=None, 335 | ): 336 | """Fetches Bundles data from the API. Function arguments will be 337 | passed as API query parameters, 338 | without modification. 339 | 340 | OpenSea API Bundles query parameters: 341 | https://docs.opensea.io/reference/retrieving-bundles 342 | 343 | There's one extra optional argument: 344 | export_file_name (str, optional): Exports the JSON data into a the 345 | specified file. 346 | 347 | Returns: 348 | [dict]: Bundles data 349 | """ 350 | query_params = { 351 | "on_sale": on_sale, 352 | "owner": owner, 353 | "asset_contract_address": asset_contract_address, 354 | "asset_contract_addresses": asset_contract_addresses, 355 | "token_ids": token_ids, 356 | "limit": self.MAX_API_ITEMS if limit is None else limit, 357 | "offset": offset, 358 | } 359 | return super()._make_request(query_params, export_file_name) 360 | -------------------------------------------------------------------------------- /opensea/opensea_api.py: -------------------------------------------------------------------------------- 1 | import time 2 | import requests 3 | from datetime import datetime 4 | from opensea import utils 5 | 6 | 7 | class OpenseaAPI: 8 | 9 | MAX_EVENT_ITEMS = 300 10 | MAX_ASSET_ITEMS = 50 11 | MAX_COLLECTION_ITEMS = 300 12 | MAX_BUNDLE_ITEMS = 50 13 | MAX_LISTING_ITEMS = 50 14 | MAX_OFFER_ITEMS = 50 15 | 16 | def __init__(self, base_url="https://api.opensea.io/api", apikey=None, 17 | version="v1"): 18 | """Base class to interact with the OpenSea API and fetch NFT data. 19 | 20 | Args: 21 | base_url (str): OpenSea API base URL. Defaults to 22 | "https://api.opensea.io/api". 23 | apikey (str): OpenSea API key (you need to request one) 24 | version (str, optional): API version. Defaults to "v1". 25 | """ 26 | self.api_url = f"{base_url}/{version}" 27 | self.apikey = apikey 28 | 29 | def _make_request(self, endpoint=None, params=None, export_file_name="", 30 | return_response=False): 31 | """Makes a request to the OpenSea API and returns either a response 32 | object or dictionary. 33 | 34 | Args: 35 | endpoint (str, optional): API endpoint to use for the request. 36 | params (dict, optional): Query parameters to include in the 37 | request. Defaults to None. 38 | export_file_name (str, optional): In case you want to download the 39 | data into a file, 40 | specify the filename here. Eg. 'export.json'. Be default, no file 41 | is created. 42 | return_response (bool, optional): Set it True if you want it to 43 | return the actual response object. 44 | By default, it's False, which means a dictionary will be returned. 45 | next_url (str, optional): If you want to paginate, provide the 46 | `next` value here (this is a URL) OpenSea provides in the response. 47 | If this argument is provided, `endpoint` will be ignored. 48 | 49 | Raises: 50 | ValueError: returns the error message from the API in case one 51 | (or more) of your request parameters are incorrect. 52 | HTTPError: Unauthorized access. Try using an API key. 53 | ConnectionError: your request got blocked by the server (try 54 | using an API key if you keep getting this error) 55 | TimeoutError: your request timed out (try rate limiting) 56 | 57 | Returns: 58 | Data sent back from the API. Either a response or dict object 59 | depending on the `return_response` argument. 60 | """ 61 | if endpoint is None: 62 | raise ValueError( 63 | """You need to define an `endpoint` when 64 | making a request!""" 65 | ) 66 | 67 | headers = {"X-API-KEY": self.apikey} 68 | url = f"{self.api_url}/{endpoint}" 69 | response = requests.get(url, headers=headers, params=params) 70 | if response.status_code == 400: 71 | raise ValueError(response.text) 72 | elif response.status_code == 401: 73 | raise requests.exceptions.HTTPError(response.text) 74 | elif response.status_code == 403: 75 | raise ConnectionError("The server blocked access.") 76 | elif response.status_code == 495: 77 | raise requests.exceptions.SSLError("SSL certificate error") 78 | elif response.status_code == 504: 79 | raise TimeoutError("The server reported a gateway time-out error.") 80 | 81 | if export_file_name != "": 82 | utils.export_file(response.content, export_file_name) 83 | if return_response: 84 | return response 85 | return response.json() 86 | 87 | def events( 88 | self, 89 | asset_contract_address=None, 90 | collection_slug=None, 91 | token_id=None, 92 | account_address=None, 93 | event_type=None, 94 | only_opensea=False, 95 | auction_type=None, 96 | limit=None, 97 | occurred_before=None, 98 | occurred_after=None, 99 | collection_editor=None, 100 | export_file_name="", 101 | ): 102 | """Fetches Events data from the API. Function arguments will be passed 103 | as API query parameters. 104 | 105 | OpenSea API Events query parameters: 106 | https://docs.opensea.io/reference/retrieving-asset-events 107 | 108 | All arguments will be passed without modification, except 109 | *occurred_before* and *occurred_after*. For these two args, you need 110 | to use datetime objects when calling this function. 111 | 112 | There's one extra optional argument: 113 | export_file_name (str, optional): Exports the JSON data into the 114 | specified file. 115 | 116 | Returns: 117 | [dict]: Events data 118 | """ 119 | if occurred_before is not None and not isinstance(occurred_before, 120 | datetime): 121 | raise ValueError("`occurred_before` must be a datetime object") 122 | 123 | if occurred_after is not None and not isinstance(occurred_after, 124 | datetime): 125 | raise ValueError("`occurred_after` must be a datetime object") 126 | 127 | query_params = { 128 | "asset_contract_address": asset_contract_address, 129 | "collection_slug": collection_slug, 130 | "token_id": token_id, 131 | "account_address": account_address, 132 | "event_type": event_type, 133 | "only_opensea": only_opensea, 134 | "auction_type": auction_type, 135 | "collection_editor": collection_editor, 136 | "limit": self.MAX_EVENT_ITEMS if limit is None else limit, 137 | } 138 | if occurred_before is not None: 139 | query_params["occurred_before"] = occurred_before.timestamp() 140 | 141 | if occurred_after is not None: 142 | query_params["occurred_after"] = occurred_after.timestamp() 143 | return self._make_request("events", query_params, export_file_name) 144 | 145 | def events_backfill( 146 | self, 147 | start, 148 | until, 149 | rate_limiting=2, 150 | asset_contract_address=None, 151 | collection_slug=None, 152 | token_id=None, 153 | account_address=None, 154 | event_type=None, 155 | only_opensea=False, 156 | auction_type=None, 157 | limit=None, 158 | collection_editor=None, 159 | ): 160 | """ 161 | EXPERIMENTAL FUNCTION! 162 | 163 | Expected behaviour: 164 | 165 | Download events and paginate over multiple pages until the given 166 | time is reached. Pagination happens **backwards** (so you can use this 167 | function to **backfill** events data eg. into a database) from `start` 168 | until `until`. 169 | 170 | The function returns a generator. 171 | 172 | Args: 173 | start (datetime): A point in time where you want to start 174 | downloading data from. 175 | until (datetime): How much data do you want? 176 | How much do you want to go back in time? This datetime value will 177 | provide that threshold. 178 | rate_limiting (int, optional): Seconds to wait between requests. 179 | Defaults to 2. 180 | 181 | Other parameters are available (all of the `events` endpoint 182 | parameters) and they are documented in the OpenSea docs 183 | https://docs.opensea.io/reference/retrieving-asset-events 184 | 185 | Yields: 186 | dictionary: event data 187 | """ 188 | if not isinstance(until, datetime) or not isinstance(start, datetime): 189 | raise ValueError("`until` and `start` must be datetime objects") 190 | 191 | if until > start: 192 | raise ValueError( 193 | """`start` must be a later point in time 194 | than `until`""" 195 | ) 196 | 197 | query_params = { 198 | "asset_contract_address": asset_contract_address, 199 | "collection_slug": collection_slug, 200 | "token_id": token_id, 201 | "account_address": account_address, 202 | "event_type": event_type, 203 | "only_opensea": only_opensea, 204 | "auction_type": auction_type, 205 | "limit": self.MAX_EVENT_ITEMS if limit is None else limit, 206 | "occurred_before": start, 207 | "collection_editor": collection_editor, 208 | } 209 | 210 | # make the first request to get the `next` cursor value 211 | first_request = self._make_request("events", query_params) 212 | yield first_request 213 | query_params["cursor"] = first_request["next"] 214 | 215 | # paginate 216 | while True: 217 | time.sleep(rate_limiting) 218 | data = self._make_request("events", query_params) 219 | 220 | # update the `next` parameter for the upcoming request 221 | query_params["cursor"] = data["next"] 222 | 223 | time_field = data["asset_events"][0]["created_date"] 224 | current_time = utils.str_to_datetime_utc(time_field) 225 | if current_time >= until: 226 | yield data 227 | else: 228 | break 229 | yield None 230 | 231 | def asset( 232 | self, 233 | asset_contract_address, 234 | token_id, 235 | account_address=None, 236 | include_orders=False, 237 | export_file_name="", 238 | ): 239 | """Fetches Asset data from the API. 240 | 241 | Args: 242 | OpenSea API Asset query parameters: 243 | https://docs.opensea.io/reference/retrieving-a-single-asset 244 | 245 | Extra args: 246 | export_file_name (str, optional): Exports the JSON data into a the 247 | specified file. 248 | 249 | Returns: 250 | [dict]: Single asset data 251 | """ 252 | endpoint = f"asset/{asset_contract_address}/{token_id}" 253 | query_params = {"account_address": account_address, 254 | "include_orders": include_orders} 255 | return self._make_request(endpoint, query_params, export_file_name) 256 | 257 | def assets( 258 | self, 259 | owner=None, 260 | token_ids=[], 261 | asset_contract_address=None, 262 | asset_contract_addresses=None, 263 | order_by=None, 264 | order_direction=None, 265 | offset=None, 266 | limit=None, 267 | collection=None, 268 | include_orders=False, 269 | pagination=False, 270 | rate_limiting=2, 271 | export_file_name="", 272 | ): 273 | """Fetches assets data from the API. Function arguments will be passed 274 | as API query parameters, without modification. 275 | 276 | OpenSea API Assets query parameters: 277 | https://docs.opensea.io/reference/getting-assets 278 | 279 | There are extra optional arguments: 280 | pagination (boolean, optional): Whether you want to get only the first 281 | page of assets, or all of them. If it's `True` it will use the 282 | cursor-based pagination provided by OpenSea. Defaults to False. 283 | rate_limiting (int, optional): Only relevant if pagination is `True`. 284 | It applies a rate limitation in-between requests. Defaults to 2 sec. 285 | export_file_name (str, optional): Exports the JSON data into the 286 | specified file. If pagination is `True` this argument is ignored. 287 | 288 | Returns: 289 | [dict]: Assets data 290 | """ 291 | query_params = { 292 | "owner": owner, 293 | "token_ids": token_ids, 294 | "asset_contract_address": asset_contract_address, 295 | "asset_contract_addresses": asset_contract_addresses, 296 | "order_by": order_by, 297 | "order_direction": order_direction, 298 | "offset": offset, 299 | "limit": self.MAX_ASSET_ITEMS if limit is None else limit, 300 | "collection": collection, 301 | "include_orders": include_orders 302 | } 303 | if pagination: 304 | # make the first request to get the `next` cursor value 305 | first_request = self._make_request("assets", query_params) 306 | yield first_request 307 | query_params["cursor"] = first_request.get("next") 308 | 309 | # paginate 310 | while True: 311 | time.sleep(rate_limiting) 312 | if query_params["cursor"] is not None: 313 | response = self._make_request("assets", query_params) 314 | yield response 315 | query_params["cursor"] = response.get("next") 316 | else: 317 | break # stop pagination if there is no next page 318 | else: 319 | return self._make_request("assets", query_params, export_file_name) 320 | 321 | def contract(self, asset_contract_address, export_file_name=""): 322 | """Fetches asset contract data from the API. 323 | 324 | OpenSea API Asset Contract query parameters: 325 | https://docs.opensea.io/reference/retrieving-a-single-contract 326 | 327 | Args: 328 | asset_contract_address (str): Contract address of the NFT. 329 | export_file_name (str, optional): Exports the JSON data into a the 330 | specified file. 331 | 332 | Returns: 333 | [dict]: Single asset contract data 334 | """ 335 | endpoint = f"asset_contract/{asset_contract_address}" 336 | return self._make_request(endpoint, export_file_name=export_file_name) 337 | 338 | def collection(self, collection_slug, export_file_name=""): 339 | """Fetches collection data from the API. 340 | 341 | OpenSea API Collection query parameters: 342 | https://docs.opensea.io/reference/retrieving-a-single-collection 343 | 344 | Args: 345 | collection_slug (str): Collection slug (unique identifer) 346 | export_file_name (str, optional): Exports the JSON data into a the 347 | specified file. 348 | 349 | Returns: 350 | [dict]: Single collection data 351 | """ 352 | endpoint = f"collection/{collection_slug}" 353 | return self._make_request(endpoint, export_file_name=export_file_name) 354 | 355 | def collection_stats(self, collection_slug, export_file_name=""): 356 | """Fetches collection stats data from the API. 357 | 358 | OpenSea API Collection Stats query parameters: 359 | https://docs.opensea.io/reference/retrieving-collection-stats 360 | 361 | Args: 362 | export_file_name (str, optional): Exports the JSON data into the 363 | specified file. 364 | 365 | Returns: 366 | [dict]: Collection stats 367 | """ 368 | endpoint = f"collection/{collection_slug}/stats" 369 | return self._make_request(endpoint, export_file_name=export_file_name) 370 | 371 | def collections( 372 | self, asset_owner=None, offset=None, limit=None, export_file_name="" 373 | ): 374 | """Fetches Collections data from the API. Function arguments will be 375 | passed as API query parameters, 376 | without modification. 377 | 378 | OpenSea API Collections query parameters: 379 | https://docs.opensea.io/reference/retrieving-collections 380 | 381 | There's one extra optional argument: 382 | export_file_name (str, optional): Exports the JSON data into a the 383 | specified file. 384 | 385 | Returns: 386 | [dict]: Collections data 387 | """ 388 | query_params = { 389 | "asset_owner": asset_owner, 390 | "offset": offset, 391 | "limit": self.MAX_COLLECTION_ITEMS if limit is None else limit, 392 | } 393 | return self._make_request("collections", query_params, 394 | export_file_name) 395 | 396 | def bundles( 397 | self, 398 | on_sale=None, 399 | owner=None, 400 | asset_contract_address=None, 401 | asset_contract_addresses=[], 402 | token_ids=[], 403 | limit=None, 404 | export_file_name="", 405 | offset=None, 406 | ): 407 | """Fetches Bundles data from the API. Function arguments will be 408 | passed as API query parameters, 409 | without modification. 410 | 411 | OpenSea API Bundles query parameters: 412 | https://docs.opensea.io/reference/retrieving-bundles 413 | 414 | There's one extra optional argument: 415 | export_file_name (str, optional): Exports the JSON data into a the 416 | specified file. 417 | 418 | Returns: 419 | [dict]: Bundles data 420 | """ 421 | query_params = { 422 | "on_sale": on_sale, 423 | "owner": owner, 424 | "asset_contract_address": asset_contract_address, 425 | "asset_contract_addresses": asset_contract_addresses, 426 | "token_ids": token_ids, 427 | "limit": self.MAX_BUNDLE_ITEMS if limit is None else limit, 428 | "offset": offset, 429 | } 430 | return self._make_request("bundles", query_params, export_file_name) 431 | 432 | def listings( 433 | self, asset_contract_address, token_id, limit=None, export_file_name="" 434 | ): 435 | """Fetches Listings data for an asset from the API. Function arguments 436 | will be passed as API query parameters, without modification. 437 | 438 | OpenSea API Listings query parameters: 439 | https://docs.opensea.io/reference/asset-listings 440 | 441 | There's one extra optional argument: 442 | export_file_name (str, optional): Exports the JSON data into a the 443 | specified file. 444 | 445 | Returns: 446 | [dict]: Listings data 447 | """ 448 | query_params = { 449 | "limit": self.MAX_LISTING_ITEMS if limit is None else limit 450 | } 451 | endpoint = f"asset/{asset_contract_address}/{token_id}/listings" 452 | return self._make_request(endpoint, query_params, export_file_name) 453 | 454 | def offers(self, asset_contract_address, token_id, limit=None, 455 | export_file_name=""): 456 | """Fetches Offers data for an asset from the API. Function arguments 457 | will be passed as API query parameters, without modification. 458 | 459 | OpenSea API Listings query parameters: 460 | https://docs.opensea.io/reference/asset-offers 461 | 462 | There's one extra optional argument: 463 | export_file_name (str, optional): Exports the JSON data into a the 464 | specified file. 465 | 466 | Returns: 467 | [dict]: Offers data 468 | """ 469 | query_params = { 470 | "limit": self.MAX_OFFER_ITEMS if limit is None else limit 471 | } 472 | endpoint = f"asset/{asset_contract_address}/{token_id}/offers" 473 | return self._make_request(endpoint, query_params, export_file_name) 474 | -------------------------------------------------------------------------------- /opensea/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | 3 | 4 | def export_file(content, file_name): 5 | """Creates a new file with the specified content and file name. If the file 6 | already exists, overwrites it. 7 | 8 | Args: 9 | content (str or bytes): Content to be inserted into the file. 10 | file_name (str): Name of the file to be created. Eg. 'export.json'. 11 | """ 12 | with open(file_name, "wb") as f: 13 | f.write(content) 14 | 15 | 16 | def str_to_datetime_utc(str): 17 | """Converts a string into UTC datetime object. 18 | 19 | Args: 20 | str (str): String timestamp. 21 | 22 | Returns: 23 | datetime: Datetime object. 24 | """ 25 | return datetime.fromisoformat(str).replace(tzinfo=timezone.utc) 26 | 27 | 28 | def datetime_utc(year, month, day, hour, minute): 29 | """Returns a datetime object in UTC timezone 30 | 31 | Args: 32 | year (int): eg. 2021 33 | month (int): between 1-12 34 | day (int): between 1-7 35 | hour (int): between 0-23 36 | minute (int): between 0-59 37 | 38 | Returns: 39 | datetime 40 | """ 41 | return datetime(year, month, day, hour, minute, tzinfo=timezone.utc) 42 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==21.1 2 | bump2version==0.5.11 3 | wheel==0.38.1 4 | watchdog==0.9.0 5 | flake8==3.7.8 6 | tox==3.14.0 7 | coverage==4.5.4 8 | Sphinx==3.1 9 | myst-parser==0.15.2 10 | sphinx-rtd-theme==1.0.0 11 | twine==1.14.0 12 | pytest==6.2.4 13 | black==21.7b0 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.7 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:opensea/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [tool:pytest] 21 | collect_ignore = ['setup.py'] 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.md') as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open('HISTORY.md') as history_file: 11 | history = history_file.read() 12 | 13 | requirements = ['requests>=2.26.0'] 14 | 15 | test_requirements = ['pytest>=3', ] 16 | 17 | setup( 18 | author="Attila Toth", 19 | author_email='hello@attilatoth.dev', 20 | python_requires='>=3.6', 21 | classifiers=[ 22 | 'Development Status :: 4 - Beta', 23 | 'Intended Audience :: Developers', 24 | 'Environment :: Web Environment', 25 | 'License :: OSI Approved :: MIT License', 26 | 'Natural Language :: English', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Programming Language :: Python :: 3.7', 31 | 'Programming Language :: Python :: 3.8', 32 | 'Topic :: Internet', 33 | 'Topic :: Internet :: WWW/HTTP :: HTTP Servers', 34 | 'Topic :: Office/Business :: Financial', 35 | 'Topic :: Software Development :: Libraries', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | 'Topic :: Utilities' 38 | 39 | ], 40 | description="Python 3 wrapper for the OpenSea NFT API", 41 | install_requires=requirements, 42 | license="MIT license", 43 | long_description=readme + "\n\n" + history, 44 | long_description_content_type="text/markdown", 45 | include_package_data=True, 46 | keywords=['opensea', 'nft', 'non fungible token', 'crypto'], 47 | maintainer='Attila Toth', 48 | maintainer_email='hello@attilatoth.dev', 49 | name='opensea-api', 50 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 51 | test_suite='tests', 52 | tests_require=test_requirements, 53 | url='https://github.com/zseta/python-opensea', 54 | project_urls={ 55 | 'Documentation': 'https://opensea-api.attilatoth.dev', 56 | 'Source': 'https://github.com/zseta/python-opensea' 57 | }, 58 | version='0.1.7', 59 | zip_safe=False, 60 | ) 61 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for opensea.""" 2 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38, py39, flake8 3 | 4 | [testenv:flake8] 5 | basepython = python 6 | deps = flake8 7 | commands = flake8 opensea tests --ignore E124,E125,E129,W503,W504 8 | 9 | [testenv] 10 | setenv = 11 | PYTHONPATH = {toxinidir} 12 | deps = -r {toxinidir}/requirements_dev.txt 13 | ; If you want to make tox run the tests with the same versions, create a 14 | ; requirements.txt with the pinned versions and uncomment the following line: 15 | ; -r{toxinidir}/requirements.txt 16 | commands = 17 | pip install -U pip 18 | pytest --basetemp={envtmpdir} 19 | pytest {posargs} 20 | --------------------------------------------------------------------------------