diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..cc23b05 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,41 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Build package + run: uv build + + - name: Upload artifacts + uses: actions/upload-artifact@v5 + with: + name: python-package-distributions + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # Required for trusted publishing + + steps: + - name: Download artifacts + uses: actions/download-artifact@v5 + with: + name: python-package-distributions + path: dist/ + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python-smoke-test.yml b/.github/workflows/python-smoke-test.yml index a3da3e2..b89ec84 100644 --- a/.github/workflows/python-smoke-test.yml +++ b/.github/workflows/python-smoke-test.yml @@ -10,22 +10,34 @@ permissions: contents: read jobs: - build: - + # Live smoke test against the Freesound API on every supported Python version. + # The API is rate limited (60 requests/minute, 2000/day per key, see + # https://freesound.org/docs/api/) and one run of examples.py makes ~20 requests, + # so the runs are serialized and spaced out. + smoke-test: runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - name: Install uv and set up Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v7 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} - name: Install dependencies + run: uv sync + - name: Check the package imports and builds run: | - python -m pip install --upgrade pip - pip install -e . + uv run python -c "import freesound" + uv build + - name: Wait to stay under the API rate limit + run: sleep 30 - name: Run smoke test env: FREESOUND_API_KEY: ${{ secrets.FREESOUND_API_KEY }} run: | - python examples.py + uv run python examples.py diff --git a/.gitignore b/.gitignore index 17a8d54..f110c92 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,198 @@ build *.pyc *.bak + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# uv +uv.lock + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + + +tmp/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/Readme.md b/Readme.md index 976f3c6..f439e95 100644 --- a/Readme.md +++ b/Readme.md @@ -29,18 +29,25 @@ for sound in results: ``` ## Installation -1) clone or download -2) run: +The easiest way to install is using pip: + ``` -python setup.py install +pip install freesound ``` -Alternatively you should also be able to install directly from GitHub with: +Alternatively, you can install directly from GitHub: + ``` pip install git+https://github.com/MTG/freesound-python ``` +Or clone the repository and install manually: + +``` +python -m pip install . +``` + ## Advanced usage ### Modifying the requests' session: diff --git a/examples.py b/examples.py index f4bd961..8581062 100644 --- a/examples.py +++ b/examples.py @@ -25,12 +25,11 @@ # Get sound info example specifying some request parameters print("Sound info specifying some request parameters:") print("-----------") -sound = freesound_client.get_sound( - 6, fields="id,name,username,duration,spectral_centroid") +sound = freesound_client.get_sound(6, fields="id,name,username,duration,spectral_centroid") print("Getting sound:", sound.name) print("Username:", sound.username) print("Duration:", str(sound.duration), "(s)") -print("Spectral centroid:",str(sound.spectral_centroid), "(Hz)") +print("Spectral centroid:", str(sound.spectral_centroid), "(Hz)") print() # Get sound analysis example @@ -80,15 +79,16 @@ print("Num results:", results_pager.count) for sound in results_pager: - print("\t-", sound.name, "by", sound.username, "with spectral centroid of", sound.spectral_centroid, "Hz and pitch of", sound.pitch, "Hz") + print( + "\t-", sound.name, "by", sound.username, "with spectral centroid of", sound.spectral_centroid, + "Hz and pitch of", sound.pitch, "Hz" + ) print() # We can use the search endpoint for a similarity search which also filters results print("Searching for similar sounds and filtering by a descriptor value:") print("---------------") -results_pager = freesound_client.search( - page_size=10, fields="name,username", similar_to=6, filter="pitch:[110 TO 180]" -) +results_pager = freesound_client.search(page_size=10, fields="name,username", similar_to=6, filter="pitch:[110 TO 180]") for similar_sound in results_pager: print("\t-", similar_sound.name, "by", similar_sound.username) print() @@ -97,7 +97,20 @@ # Note that "similarity_space" must match with the similarity space used to extract the target vector print("Searching for sounds similar to a target vector:") print("---------------") -target_vector = [0.84835537,-0.06019006,0.35139768,-0.01221892,-0.23172645,-0.03798686,-0.03869437,0.02199453,-0.10143687,-0.03342770,0.05464298,-0.02654354,0.05424048,0.04912830,0.01449411,-0.02995046,0.04584143,0.03731462,0.06914231,-0.00702387,-0.02202889,0.01644059,-0.00153376,0.11042101,0.05432773,0.05736105,-0.03779107,-0.00909068,-0.08996461,-0.04300615,0.05610843,0.02214170,-0.02155820,0.05158299,-0.00717155,-0.04345755,-0.00519616,0.02887811,-0.02205723,0.01658933,0.02485796,-0.06228176,0.03574570,-0.04556302,-0.00497004,0.00300936,-0.01974736,-0.01391953,-0.02898939,0.01041939,-0.02836645,0.00853050,-0.03129587,0.00454572,0.00898315,-0.01371797,-0.00918297,-0.01049032,0.02800968,-0.04248178,0.02648444,-0.01034762,0.02105908,-0.01137279,0.02845560,-0.04284714,0.00797986,0.00973879,-0.00850114,-0.01093731,-0.00629640,-0.01862817,0.00829806,0.01137537,0.02601988,0.03015542,-0.01091145,0.00547907,-0.00426657,0.01001693,0.00793383,0.00082211,-0.02848534,-0.00823537,0.01392606,-0.02012341,-0.00788319,0.02797560,-0.01470957,-0.01917517,-0.01177181,0.00952904,-0.00223396,-0.01586017,-0.00566903,0.01150901,-0.00361810,-0.00257769,-0.01509761,0.00552032] +target_vector = [ + 0.84835537, -0.06019006, 0.35139768, -0.01221892, -0.23172645, -0.03798686, -0.03869437, 0.02199453, -0.10143687, + -0.03342770, 0.05464298, -0.02654354, 0.05424048, 0.04912830, 0.01449411, -0.02995046, 0.04584143, 0.03731462, + 0.06914231, -0.00702387, -0.02202889, 0.01644059, -0.00153376, 0.11042101, 0.05432773, 0.05736105, -0.03779107, + -0.00909068, -0.08996461, -0.04300615, 0.05610843, 0.02214170, -0.02155820, 0.05158299, -0.00717155, -0.04345755, + -0.00519616, 0.02887811, -0.02205723, 0.01658933, 0.02485796, -0.06228176, 0.03574570, -0.04556302, -0.00497004, + 0.00300936, -0.01974736, -0.01391953, -0.02898939, 0.01041939, -0.02836645, 0.00853050, -0.03129587, 0.00454572, + 0.00898315, -0.01371797, -0.00918297, -0.01049032, 0.02800968, -0.04248178, 0.02648444, -0.01034762, 0.02105908, + -0.01137279, 0.02845560, -0.04284714, 0.00797986, 0.00973879, -0.00850114, -0.01093731, -0.00629640, -0.01862817, + 0.00829806, 0.01137537, 0.02601988, 0.03015542, -0.01091145, 0.00547907, -0.00426657, 0.01001693, 0.00793383, + 0.00082211, -0.02848534, -0.00823537, 0.01392606, -0.02012341, -0.00788319, 0.02797560, -0.01470957, -0.01917517, + -0.01177181, 0.00952904, -0.00223396, -0.01586017, -0.00566903, 0.01150901, -0.00361810, -0.00257769, -0.01509761, + 0.00552032 +] results_pager = freesound_client.search( page_size=10, fields="name,username", similar_to=target_vector, similarity_space="freesound_classic" ) @@ -108,10 +121,7 @@ # Search sounds and sort them by distance to a given audio descriptors target print("Sort sounds by distance to audio descriptors target:") print("---------------") -results_pager = freesound_client.search( - filter="pitch_var:[* TO 20]", - sort='pitch_salience:1.0,pitch:440' -) +results_pager = freesound_client.search(filter="pitch_var:[* TO 20]", sort='pitch_salience:1.0,pitch:440') for sound in results_pager: print("\t-", sound.name, "by", sound.username, "distance:", sound.distance_to_target) @@ -138,9 +148,7 @@ print("-----------") user = freesound_client.get_user("Headphaze") print("User name:", user.username) -results_pager = user.get_sounds( - page_size=10, fields="name,username,samplerate,duration" -) +results_pager = user.get_sounds(page_size=10, fields="name,username,samplerate,duration") print("Num results:", results_pager.count) print("\t----- PAGE 1 -----") @@ -179,9 +187,7 @@ print("-----------") pack = freesound_client.get_pack(3524) print("Pack name:", pack.name) -results_pager = pack.get_sounds( - page_size=5, fields="id,name,username,duration,spectral_flatness" -) +results_pager = pack.get_sounds(page_size=5, fields="id,name,username,duration,spectral_flatness") print("Num results:", results_pager.count) print("\t----- PAGE 1 -----") for sound in results_pager: diff --git a/freesound.py b/freesound.py index 17eace5..e39d0bb 100644 --- a/freesound.py +++ b/freesound.py @@ -123,7 +123,7 @@ def search(self, **params): vector = params['similar_to'] vector_str = ','.join(['{0:.5f}'.format(v) for v in vector]) params['similar_to'] = '[' + vector_str + ']' - + uri = URIS.uri(URIS.SEARCH) return FSRequest.request(uri, params, self, Pager) diff --git a/pyproject.toml b/pyproject.toml index 5bf0d9b..d071598 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,45 @@ +[project] +name = "freesound" +version = "1.1" +description = "A Python client for the Freesound APIv2" +readme = "Readme.md" +requires-python = ">=3.9" +license = "MIT" +license-files = ["COPYING.txt"] +dependencies = [ + "requests>=2.27,<3.0", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Multimedia :: Sound/Audio", +] + +[project.urls] +Homepage = "https://github.com/MTG/freesound-python" +Repository = "https://github.com/MTG/freesound-python.git" +Issues = "https://github.com/MTG/freesound-python/issues" +Documentation = "https://freesound.org/docs/api/" + +[dependency-groups] +dev = [ + "yapf", +] + +[build-system] +requires = ["hatchling >= 1.27"] +build-backend = "hatchling.build" + +[tool.hatch.build] +include = ["freesound.py"] + [tool.yapf] based_on_style = "google" spaces_before_comment = 4 diff --git a/setup.py b/setup.py deleted file mode 100644 index b2f6273..0000000 --- a/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from distutils.core import setup - -setup( - name="freesound-python", - version="1.1", - py_modules=["freesound"], - install_requires=["requests<3.0,>2.27"], - python_requires=">=3.6", -)