Compare commits

..

No commits in common. "main" and "5.2.0" have entirely different histories.
main ... 5.2.0

29 changed files with 223 additions and 296 deletions

2
.flake8 Normal file
View file

@ -0,0 +1,2 @@
[flake8]
max-line-length = 88

13
.github/renovate.json vendored
View file

@ -1,13 +1,16 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["changelog: skip", "dependencies"],
"extends": [
"config:base"
],
"labels": [
"changelog: skip",
"dependencies"
],
"packageRules": [
{
"groupName": "github-actions",
"matchManagers": ["github-actions"],
"separateMajorMinor": "false"
}
],
"schedule": ["on the first day of the month"]
]
}

View file

@ -21,28 +21,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: hynek/build-and-inspect-python-package@v2
- uses: hynek/build-and-inspect-python-package@v1
# Upload to Test PyPI on every commit on main.
release-test-pypi:
name: Publish in-dev package to test.pypi.org
if: |
github.repository_owner == 'pylast'
&& github.event_name == 'push'
&& github.ref == 'refs/heads/main'
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build-package
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
@ -55,18 +53,17 @@ jobs:
# Upload to real PyPI on GitHub Releases.
release-pypi:
name: Publish released package to pypi.org
if: |
github.repository_owner == 'pylast'
&& github.event.action == 'published'
if: github.event.action == 'published'
runs-on: ubuntu-latest
needs: build-package
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: Packages
path: dist

View file

@ -1,8 +1,5 @@
name: Sync labels
permissions:
pull-requests: write
on:
push:
branches:
@ -15,7 +12,7 @@ jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: micnncim/action-label-syncer@v1
with:
prune: false

View file

@ -2,10 +2,6 @@ name: Lint
on: [push, pull_request, workflow_dispatch]
env:
FORCE_COLOR: 1
PIP_DISABLE_PIP_VERSION_CHECK: 1
permissions:
contents: read
@ -14,9 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
- uses: pre-commit/action@v3.0.1
- uses: pre-commit/action@v3.0.0

View file

@ -29,6 +29,6 @@ jobs:
runs-on: ubuntu-latest
steps:
# Drafts your next release notes as pull requests are merged into "main"
- uses: release-drafter/release-drafter@v6
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -10,10 +10,9 @@ jobs:
permissions:
issues: write
pull-requests: write
steps:
- uses: mheap/github-action-required-labels@v5
- uses: mheap/github-action-required-labels@v4
with:
mode: minimum
count: 1

View file

@ -11,18 +11,19 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.10", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
@ -40,7 +41,7 @@ jobs:
PYLAST_USERNAME: ${{ secrets.PYLAST_USERNAME }}
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v3
with:
flags: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}

View file

@ -1,74 +1,64 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
- repo: https://github.com/asottile/pyupgrade
rev: v3.4.0
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/asottile/blacken-docs
rev: 1.18.0
rev: 1.13.0
hooks:
- id: blacken-docs
args: [--target-version=py38]
additional_dependencies: [black]
additional_dependencies: [black==23.3.0]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: python-no-log-warn
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: forbid-submodules
- id: trailing-whitespace
exclude: .github/(ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE).md
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.6
hooks:
- id: check-github-workflows
- id: check-renovate
- repo: https://github.com/rhysd/actionlint
rev: v1.7.1
hooks:
- id: actionlint
- id: requirements-txt-fixer
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 2.1.3
rev: 0.10.0
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.18
rev: v0.13
hooks:
- id: validate-pyproject
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.3.1
rev: 1.3.0
hooks:
- id: tox-ini-fmt
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
exclude: .github/(ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE).md
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
ci:
autoupdate_schedule: quarterly

9
.scrutinizer.yml Normal file
View file

@ -0,0 +1,9 @@
checks:
python:
code_rating: true
duplicate_code: true
filter:
excluded_paths:
- '*/test/*'
tools:
external_code_coverage: true

View file

@ -12,125 +12,117 @@ See GitHub Releases:
## Changed
- Fix unsafe creation of temp file for caching, and improve exception raising (#356)
@kvanzuijlen
- [pre-commit.ci] pre-commit autoupdate (#362) @pre-commit-ci
* Fix unsafe creation of temp file for caching, and improve exception raising (#356) @kvanzuijlen
* [pre-commit.ci] pre-commit autoupdate (#362) @pre-commit-ci
## [4.1.0] - 2021-01-04
## Added
- Add support for streaming (#336) @kvanzuijlen
- Add Python 3.9 final to Travis CI (#350) @sheetalsingala
* Add support for streaming (#336) @kvanzuijlen
* Add Python 3.9 final to Travis CI (#350) @sheetalsingala
## Changed
- Update copyright year (#360) @hugovk
- Replace Travis CI with GitHub Actions (#352) @hugovk
- [pre-commit.ci] pre-commit autoupdate (#359) @pre-commit-ci
* Update copyright year (#360) @hugovk
* Replace Travis CI with GitHub Actions (#352) @hugovk
* [pre-commit.ci] pre-commit autoupdate (#359) @pre-commit-ci
## Fixed
- Set limit to 50 by default, not 1 (#355) @hugovk
* Set limit to 50 by default, not 1 (#355) @hugovk
## [4.0.0] - 2020-10-07
## Added
- Add support for Python 3.9 (#347) @hugovk
* Add support for Python 3.9 (#347) @hugovk
## Removed
- Remove deprecated `Artist.get_cover_image`, `User.get_artist_tracks` and
`STATUS_TOKEN_ERROR` (#348) @hugovk
- Drop support for EOL Python 3.5 (#346) @hugovk
* Remove deprecated `Artist.get_cover_image`, `User.get_artist_tracks` and `STATUS_TOKEN_ERROR` (#348) @hugovk
* Drop support for EOL Python 3.5 (#346) @hugovk
## [3.3.0] - 2020-06-25
### Added
- `User.get_now_playing`: Add album and cover image to info (#330) @hugovk
* `User.get_now_playing`: Add album and cover image to info (#330) @hugovk
### Changed
- Improve handling of error responses from the API (#327) @spiritualized
* Improve handling of error responses from the API (#327) @spiritualized
### Deprecated
- Deprecate `Artist.get_cover_image`, they're no longer available from Last.fm (#332)
@hugovk
* Deprecate `Artist.get_cover_image`, they're no longer available from Last.fm (#332) @hugovk
### Fixed
- Fix `artist.get_bio_content()` to return `None` if bio is empty (#326) @hugovk
* Fix `artist.get_bio_content()` to return `None` if bio is empty (#326) @hugovk
## [3.2.1] - 2020-03-05
### Fixed
- Only Python 3 is supported: don't create universal wheel (#318) @hugovk
- Fix regression calling `get_recent_tracks` with `limit=None` (#320) @hugovk
- Fix `DeprecationWarning`: Please use `assertRegex` instead (#323) @hugovk
* Only Python 3 is supported: don't create universal wheel (#318) @hugovk
* Fix regression calling `get_recent_tracks` with `limit=None` (#320) @hugovk
* Fix `DeprecationWarning`: Please use `assertRegex` instead (#323) @hugovk
## [3.2.0] - 2020-01-03
### Added
- Support for Python 3.8
- Store album art URLs when you call `GetTopAlbums` ([#307])
- Retry paging through results on exception ([#297])
- More error status codes from https://last.fm/api/errorcodes ([#297])
* Support for Python 3.8
* Store album art URLs when you call `GetTopAlbums` ([#307])
* Retry paging through results on exception ([#297])
* More error status codes from https://last.fm/api/errorcodes ([#297])
### Changed
- Respect `get_recent_tracks`' limit when there's a now playing track ([#310])
- Move installable code to `src/` ([#301])
- Update `get_weekly_artist_charts` docstring: only for `User` ([#311])
- Remove Python 2 warnings, `python_requires` should be enough ([#312])
- Use setuptools_scm to simplify versioning during release ([#316])
- Various lint and test updates
* Respect `get_recent_tracks`' limit when there's a now playing track ([#310])
* Move installable code to `src/` ([#301])
* Update `get_weekly_artist_charts` docstring: only for `User` ([#311])
* Remove Python 2 warnings, `python_requires` should be enough ([#312])
* Use setuptools_scm to simplify versioning during release ([#316])
* Various lint and test updates
### Deprecated
- Last.fm's `user.getArtistTracks` has now been deprecated by Last.fm and is no longer
* Last.fm's `user.getArtistTracks` has now been deprecated by Last.fm and is no longer
available. Last.fm returns a "Deprecated - This type of request is no longer
supported" error when calling it. A future version of pylast will remove its
`User.get_artist_tracks` altogether. ([#305])
- `STATUS_TOKEN_ERROR` is deprecated and will be removed in a future version. Use
`STATUS_OPERATION_FAILED` instead.
* `STATUS_TOKEN_ERROR` is deprecated and will be removed in a future version.
Use `STATUS_OPERATION_FAILED` instead.
## [3.1.0] - 2019-03-07
### Added
- Extract username from session via new
* Extract username from session via new
`SessionKeyGenerator.get_web_auth_session_key_username` ([#290])
- `User.get_track_scrobbles` ([#298])
* `User.get_track_scrobbles` ([#298])
### Deprecated
- `User.get_artist_tracks`. Use `User.get_track_scrobbles` as a partial replacement.
([#298])
* `User.get_artist_tracks`. Use `User.get_track_scrobbles` as a partial replacement.
([#298])
## [3.0.0] - 2019-01-01
### Added
- This changelog file ([#273])
* This changelog file ([#273])
### Removed
- Support for Python 2.7 ([#265])
* Support for Python 2.7 ([#265])
- Constants `COVER_SMALL`, `COVER_MEDIUM`, `COVER_LARGE`, `COVER_EXTRA_LARGE` and
`COVER_MEGA`. Use `SIZE_SMALL` etc. instead. ([#282])
* Constants `COVER_SMALL`, `COVER_MEDIUM`, `COVER_LARGE`, `COVER_EXTRA_LARGE`
and `COVER_MEGA`. Use `SIZE_SMALL` etc. instead. ([#282])
## [2.4.0] - 2018-08-08
### Deprecated
- Support for Python 2.7 ([#265])
* Support for Python 2.7 ([#265])
[4.2.0]: https://github.com/pylast/pylast/compare/4.1.0...4.2.0
[4.1.0]: https://github.com/pylast/pylast/compare/4.0.0...4.1.0

View file

@ -32,11 +32,11 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

View file

@ -15,44 +15,50 @@ Use the pydoc utility for help on usage or see [tests/](tests/) for examples.
## Installation
Install via pip:
```sh
python3 -m pip install pylast
```
Install latest development version:
```sh
python3 -m pip install -U git+https://git.hirad.it/Hirad/pylast
python3 -m pip install -U git+https://github.com/pylast/pylast
```
Or from requirements.txt:
```txt
-e https://git.hirad.it/Hirad/pylast#egg=pylast
-e https://github.com/pylast/pylast.git#egg=pylast
```
Note:
- pyLast 5.3+ supports Python 3.8-3.13.
- pyLast 5.2+ supports Python 3.8-3.12.
- pyLast 5.1 supports Python 3.7-3.11.
- pyLast 5.0 supports Python 3.7-3.10.
- pyLast 4.3 - 4.5 supports Python 3.6-3.10.
- pyLast 4.0 - 4.2 supports Python 3.6-3.9.
- pyLast 3.2 - 3.3 supports Python 3.5-3.8.
- pyLast 3.0 - 3.1 supports Python 3.5-3.7.
- pyLast 2.2 - 2.4 supports Python 2.7.10+, 3.4-3.7.
- pyLast 2.0 - 2.1 supports Python 2.7.10+, 3.4-3.6.
- pyLast 1.7 - 1.9 supports Python 2.7, 3.3-3.6.
- pyLast 1.0 - 1.6 supports Python 2.7, 3.3-3.4.
- pyLast 0.5 supports Python 2, 3.
- pyLast < 0.5 supports Python 2.
* pyLast 5.2+ supports Python 3.8-3.12.
* pyLast 5.1 supports Python 3.7-3.11.
* pyLast 5.0 supports Python 3.7-3.10.
* pyLast 4.3 - 4.5 supports Python 3.6-3.10.
* pyLast 4.0 - 4.2 supports Python 3.6-3.9.
* pyLast 3.2 - 3.3 supports Python 3.5-3.8.
* pyLast 3.0 - 3.1 supports Python 3.5-3.7.
* pyLast 2.2 - 2.4 supports Python 2.7.10+, 3.4-3.7.
* pyLast 2.0 - 2.1 supports Python 2.7.10+, 3.4-3.6.
* pyLast 1.7 - 1.9 supports Python 2.7, 3.3-3.6.
* pyLast 1.0 - 1.6 supports Python 2.7, 3.3-3.4.
* pyLast 0.5 supports Python 2, 3.
* pyLast < 0.5 supports Python 2.
## Features
- Simple public interface.
- Access to all the data exposed by the Last.fm web services.
- Scrobbling support.
- Full object-oriented design.
- Proxy support.
- Internal caching support for some web services calls (disabled by default).
- Support for other API-compatible networks like Libre.fm.
* Simple public interface.
* Access to all the data exposed by the Last.fm web services.
* Scrobbling support.
* Full object-oriented design.
* Proxy support.
* Internal caching support for some web services calls (disabled by default).
* Support for other API-compatible networks like Libre.fm.
## Getting started
@ -81,8 +87,8 @@ network = pylast.LastFMNetwork(
)
```
Alternatively, instead of creating `network` with a username and password, you can
authenticate with a session key:
Alternatively, instead of creating `network` with a username and password,
you can authenticate with a session key:
```python
import pylast
@ -125,6 +131,7 @@ track.add_tags(("awesome", "favorite"))
# to get more help about anything and see examples of how it works
```
More examples in
<a href="https://github.com/hugovk/lastfm-tools">hugovk/lastfm-tools</a> and
[tests/](https://github.com/pylast/pylast/tree/main/tests).
@ -136,9 +143,8 @@ integration and unit tests with Last.fm, and plenty of code examples.
For integration tests you need a test account at Last.fm that will become cluttered with
test data, and an API key and secret. Either copy
[example_test_pylast.yaml](https://github.com/pylast/pylast/blob/main/example_test_pylast.yaml)
to test_pylast.yaml and fill out the credentials, or set them as environment variables
like:
[example_test_pylast.yaml](example_test_pylast.yaml) to test_pylast.yaml and fill out
the credentials, or set them as environment variables like:
```sh
export PYLAST_USERNAME=TODO_ENTER_YOURS_HERE

View file

@ -1,8 +1,8 @@
# Release Checklist
- [ ] Get `main` to the appropriate code release state.
[GitHub Actions](https://github.com/pylast/pylast/actions) should be running
cleanly for all merges to `main`.
[GitHub Actions](https://github.com/pylast/pylast/actions) should be running cleanly for
all merges to `main`.
[![Test](https://github.com/pylast/pylast/workflows/Test/badge.svg)](https://github.com/pylast/pylast/actions)
- [ ] Edit release draft, adjust text if needed:
@ -12,8 +12,7 @@
- [ ] Publish release
- [ ] Check the tagged
[GitHub Actions build](https://github.com/pylast/pylast/actions/workflows/deploy.yml)
- [ ] Check the tagged [GitHub Actions build](https://github.com/pylast/pylast/actions/workflows/deploy.yml)
has deployed to [PyPI](https://pypi.org/project/pylast/#history)
- [ ] Check installation:

View file

@ -1,4 +1,4 @@
username: TODO_ENTER_YOURS_HERE
password_hash: TODO_ENTER_YOURS_HERE
api_key: TODO_ENTER_YOURS_HERE
api_secret: TODO_ENTER_YOURS_HERE
username: TODO_ENTER_YOURS_HERE
password_hash: TODO_ENTER_YOURS_HERE
api_key: TODO_ENTER_YOURS_HERE
api_secret: TODO_ENTER_YOURS_HERE

View file

@ -15,24 +15,20 @@ keywords = [
"scrobble",
"scrobbling",
]
license = { text = "Apache-2.0" }
maintainers = [
{ name = "Hugo van Kemenade" },
]
authors = [
{ name = "Amr Hassan <amr.hassan@gmail.com> and Contributors", email = "amr.hassan@gmail.com" },
]
license = {text = "Apache-2.0"}
maintainers = [{name = "Hugo van Kemenade"}]
authors = [{name = "Amr Hassan <amr.hassan@gmail.com> and Contributors", email = "amr.hassan@gmail.com"}]
requires-python = ">=3.8"
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"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 :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet",
@ -45,16 +41,18 @@ dynamic = [
dependencies = [
"httpx",
]
optional-dependencies.tests = [
[project.optional-dependencies]
tests = [
"flaky",
"pytest",
"pytest-cov",
"pytest-random-order",
"pyyaml",
]
urls.Changelog = "https://github.com/pylast/pylast/releases"
urls.Homepage = "https://github.com/pylast/pylast"
urls.Source = "https://github.com/pylast/pylast"
[project.urls]
Changelog = "https://github.com/pylast/pylast/releases"
Homepage = "https://github.com/pylast/pylast"
Source = "https://github.com/pylast/pylast"
[tool.hatch]
version.source = "vcs"
@ -62,36 +60,5 @@ version.source = "vcs"
[tool.hatch.version.raw-options]
local_scheme = "no-local-version"
[tool.ruff]
fix = true
lint.select = [
"C4", # flake8-comprehensions
"E", # pycodestyle errors
"EM", # flake8-errmsg
"F", # pyflakes errors
"I", # isort
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PGH", # pygrep-hooks
"RUF022", # unsorted-dunder-all
"RUF100", # unused noqa (yesqa)
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # flake8-2020
]
lint.extend-ignore = [
"E203", # Whitespace before ':'
"E221", # Multiple spaces before operator
"E226", # Missing whitespace around arithmetic operator
"E241", # Multiple spaces after ','
]
lint.isort.known-first-party = [
"pylast",
]
lint.isort.required-imports = [
"from __future__ import annotations",
]
[tool.pyproject-fmt]
max_supported_python = "3.13"
[tool.isort]
profile = "black"

View file

@ -1,6 +1,6 @@
#
# pylast -
# A Python interface to Last.fm and music.lonestar.it
# A Python interface to Last.fm and Libre.fm
#
# Copyright 2008-2010 Amr Hassan
# Copyright 2013-2021 hugovk
@ -529,25 +529,25 @@ class _Network:
def scrobble(
self,
artist: str,
title: str,
timestamp: int,
album: str | None = None,
album_artist: str | None = None,
track_number: int | None = None,
duration: int | None = None,
stream_id: str | None = None,
context: str | None = None,
mbid: str | None = None,
artist,
title,
timestamp,
album=None,
album_artist=None,
track_number=None,
duration=None,
stream_id=None,
context=None,
mbid=None,
):
"""Used to add a track-play to a user's profile.
Parameters:
artist (Required) : The artist name.
title (Required) : The track name.
timestamp (Required) : The time the track started playing, in Unix
timestamp (Required) : The time the track started playing, in UNIX
timestamp format (integer number of seconds since 00:00:00,
January 1st 1970 UTC).
January 1st 1970 UTC). This must be in the UTC time zone.
album (Optional) : The album name.
album_artist (Optional) : The album artist - if this differs from
the track artist.
@ -628,6 +628,7 @@ class _Network:
class LastFMNetwork(_Network):
"""A Last.fm network object
api_key: a provided API_KEY
@ -705,7 +706,7 @@ class LastFMNetwork(_Network):
class LibreFMNetwork(_Network):
"""
A preconfigured _Network object for music.lonestar.it
A preconfigured _Network object for Libre.fm
api_key: a provided API_KEY
api_secret: a provided API_SECRET
@ -727,27 +728,27 @@ class LibreFMNetwork(_Network):
password_hash: str = "",
) -> None:
super().__init__(
name="music.lonestar.it",
homepage="https://music.lonestar.it",
ws_server=("music.lonestar.it", "/2.0/"),
name="Libre.fm",
homepage="https://libre.fm",
ws_server=("libre.fm", "/2.0/"),
api_key=api_key,
api_secret=api_secret,
session_key=session_key,
username=username,
password_hash=password_hash,
domain_names={
DOMAIN_ENGLISH: "music.lonestar.it",
DOMAIN_GERMAN: "music.lonestar.it",
DOMAIN_SPANISH: "music.lonestar.it",
DOMAIN_FRENCH: "music.lonestar.it",
DOMAIN_ITALIAN: "music.lonestar.it",
DOMAIN_POLISH: "music.lonestar.it",
DOMAIN_PORTUGUESE: "music.lonestar.it",
DOMAIN_SWEDISH: "music.lonestar.it",
DOMAIN_TURKISH: "music.lonestar.it",
DOMAIN_RUSSIAN: "music.lonestar.it",
DOMAIN_JAPANESE: "music.lonestar.it",
DOMAIN_CHINESE: "music.lonestar.it",
DOMAIN_ENGLISH: "libre.fm",
DOMAIN_GERMAN: "libre.fm",
DOMAIN_SPANISH: "libre.fm",
DOMAIN_FRENCH: "libre.fm",
DOMAIN_ITALIAN: "libre.fm",
DOMAIN_POLISH: "libre.fm",
DOMAIN_PORTUGUESE: "libre.fm",
DOMAIN_SWEDISH: "libre.fm",
DOMAIN_TURKISH: "libre.fm",
DOMAIN_RUSSIAN: "libre.fm",
DOMAIN_JAPANESE: "libre.fm",
DOMAIN_CHINESE: "libre.fm",
},
urls={
"album": "artist/%(artist)s/album/%(album)s",
@ -893,7 +894,6 @@ class _Request:
username = "" if username is None else f"?username={username}"
(host_name, host_subdir) = self.network.ws_server
timeout = httpx.Timeout(5, read=10)
if self.network.is_proxy_enabled():
client = httpx.Client(
@ -901,14 +901,12 @@ class _Request:
base_url=f"https://{host_name}",
headers=HEADERS,
proxies=self.network.proxy,
timeout=timeout,
)
else:
client = httpx.Client(
verify=SSL_CONTEXT,
base_url=f"https://{host_name}",
headers=HEADERS,
timeout=timeout,
)
try:
@ -1158,7 +1156,7 @@ class _BaseObject:
def get_wiki_published_date(self):
"""
Returns the date on which the wiki was published.
Returns the summary of the wiki.
Only for Album/Track.
"""
return self.get_wiki("published")
@ -1172,7 +1170,7 @@ class _BaseObject:
def get_wiki_content(self):
"""
Returns the content of the wiki.
Returns the summary of the wiki.
Only for Album/Track.
"""
return self.get_wiki("content")
@ -1242,10 +1240,8 @@ class _Chartable(_BaseObject):
from_date value to the to_date value.
chart_kind should be one of "album", "artist" or "track"
"""
import sys
method = ".getWeekly" + chart_kind.title() + "Chart"
chart_type = getattr(sys.modules[__name__], chart_kind.title())
chart_type = eval(chart_kind.title()) # string to type
params = self._get_params()
if from_date and to_date:
@ -1357,11 +1353,11 @@ class _Taggable(_BaseObject):
new_tags.append(tag)
for i in range(0, len(old_tags)):
if c_old_tags[i] not in c_new_tags:
if not c_old_tags[i] in c_new_tags:
to_remove.append(old_tags[i])
for i in range(0, len(new_tags)):
if c_new_tags[i] not in c_old_tags:
if not c_new_tags[i] in c_old_tags:
to_add.append(new_tags[i])
self.remove_tags(to_remove)
@ -1509,7 +1505,7 @@ class _Opus(_Taggable):
return f"{self.get_artist().get_name()} - {self.get_title()}"
def __eq__(self, other):
if type(self) is not type(other):
if type(self) != type(other):
return False
a = self.get_title().lower()
b = other.get_title().lower()
@ -1547,7 +1543,7 @@ class _Opus(_Taggable):
return self.info["image"][size]
def get_title(self, properly_capitalized: bool = False):
"""Returns the album or track title."""
"""Returns the artist or track title."""
if properly_capitalized:
self.title = _extract(
self._request(self.ws_prefix + ".getInfo", True), "name"
@ -2299,8 +2295,8 @@ class User(_Chartable):
self,
limit: int = 10,
cacheable: bool = True,
time_from: int | None = None,
time_to: int | None = None,
time_from=None,
time_to=None,
stream: bool = False,
now_playing: bool = False,
):
@ -2311,11 +2307,13 @@ class User(_Chartable):
Parameters:
limit : If None, it will try to pull all the available data.
from (Optional) : Beginning timestamp of a range - only display
scrobbles after this time, in Unix timestamp format (integer
number of seconds since 00:00:00, January 1st 1970 UTC).
scrobbles after this time, in UNIX timestamp format (integer
number of seconds since 00:00:00, January 1st 1970 UTC). This
must be in the UTC time zone.
to (Optional) : End timestamp of a range - only display scrobbles
before this time, in Unix timestamp format (integer number of
seconds since 00:00:00, January 1st 1970 UTC).
before this time, in UNIX timestamp format (integer number of
seconds since 00:00:00, January 1st 1970 UTC). This must be in
the UTC time zone.
stream: If True, it will yield tracks as soon as a page has been retrieved.
This method uses caching. Enable caching only if you're pulling a
@ -2384,7 +2382,7 @@ class User(_Chartable):
return _extract(doc, "registered")
def get_unixtime_registered(self):
"""Returns the user's registration date as a Unix timestamp."""
"""Returns the user's registration date as a UNIX timestamp."""
doc = self._request(self.ws_prefix + ".getInfo", True)
@ -2779,8 +2777,7 @@ def _collect_nodes(
main.getAttribute("totalPages") or main.getAttribute("totalpages")
)
else:
msg = "No total pages attribute"
raise PyLastError(msg)
raise PyLastError("No total pages attribute")
for node in main.childNodes:
if not node.nodeType == xml.dom.Node.TEXT_NODE and (

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pytest
import pylast

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
from flaky import flaky
import pylast

View file

@ -1,9 +1,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import re
import time

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import os
import time

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm

View file

@ -1,9 +1,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import time
import pytest
@ -138,7 +135,11 @@ class TestPyLastTrack(TestPyLastWithLastFm):
similar = track.get_similar()
# Assert
found = any(str(track.item) == "Cher - Strong Enough" for track in similar)
found = False
for track in similar:
if str(track.item) == "Madonna - Vogue":
found = True
break
assert found
def test_track_get_similar_limits(self) -> None:

View file

@ -2,8 +2,6 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import calendar
import datetime as dt
import inspect

View file

@ -1,5 +1,3 @@
from __future__ import annotations
from unittest import mock
import pytest
@ -27,7 +25,7 @@ def test_get_cache_key(artist) -> None:
@pytest.mark.parametrize("obj", [pylast.Artist("B\xe9l", mock_network())])
def test_cast_and_hash(obj) -> None:
assert isinstance(str(obj), str)
assert type(str(obj)) is str
assert isinstance(hash(obj), int)

11
tox.ini
View file

@ -3,7 +3,7 @@ requires =
tox>=4.2
env_list =
lint
py{py3, 313, 312, 311, 310, 39, 38}
py{py3, 312, 311, 310, 39, 38}
[testenv]
extras =
@ -15,14 +15,7 @@ pass_env =
PYLAST_PASSWORD_HASH
PYLAST_USERNAME
commands =
{envpython} -m pytest -v -s -W all \
--cov pylast \
--cov tests \
--cov-report html \
--cov-report term-missing \
--cov-report xml \
--random-order \
{posargs}
pytest -v -s -W all --cov pylast --cov tests --cov-report term-missing --cov-report xml --random-order {posargs}
[testenv:lint]
skip_install = true