Compare commits

...

83 commits
5.1.0 ... main

Author SHA1 Message Date
Hirad 88bb8ea789 Update README.md
Some checks failed
Release drafter / update_release_draft (push) Has been cancelled
Deploy / Build & verify package (push) Has been cancelled
Deploy / Publish in-dev package to test.pypi.org (push) Has been cancelled
Deploy / Publish released package to pypi.org (push) Has been cancelled
Lint / lint (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.8) (push) Has been cancelled
Test / test (ubuntu-latest, 3.9) (push) Has been cancelled
Test / test (ubuntu-latest, pypy3.10) (push) Has been cancelled
Test / Test successful (push) Has been cancelled
2024-07-07 09:16:54 +03:30
Hirad 6ae051157f Update README.md
Some checks are pending
Deploy / Build & verify package (push) Waiting to run
Deploy / Publish in-dev package to test.pypi.org (push) Blocked by required conditions
Deploy / Publish released package to pypi.org (push) Blocked by required conditions
Lint / lint (push) Waiting to run
Release drafter / update_release_draft (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.8) (push) Waiting to run
Test / test (ubuntu-latest, 3.9) (push) Waiting to run
Test / test (ubuntu-latest, pypy3.10) (push) Waiting to run
Test / Test successful (push) Blocked by required conditions
2024-07-07 09:16:25 +03:30
Hirad c260d7b83f change libre.fm to music.lonestar.it
Some checks are pending
Deploy / Build & verify package (push) Waiting to run
Deploy / Publish in-dev package to test.pypi.org (push) Blocked by required conditions
Deploy / Publish released package to pypi.org (push) Blocked by required conditions
Lint / lint (push) Waiting to run
Release drafter / update_release_draft (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.8) (push) Waiting to run
Test / test (ubuntu-latest, 3.9) (push) Waiting to run
Test / test (ubuntu-latest, pypy3.10) (push) Waiting to run
Test / Test successful (push) Blocked by required conditions
2024-07-07 09:03:18 +03:30
Hugo van Kemenade 25904371de
[pre-commit.ci] pre-commit autoupdate (#458) 2024-07-02 05:34:35 -06:00
Hugo van Kemenade 184d0328a9 Configure pyproject-fmt to add Python 3.13 classifier 2024-07-02 14:29:20 +03:00
pre-commit-ci[bot] aceaa69c9a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.5.0)
- [github.com/asottile/blacken-docs: 1.16.0 → 1.18.0](https://github.com/asottile/blacken-docs/compare/1.16.0...1.18.0)
- [github.com/python-jsonschema/check-jsonschema: 0.28.4 → 0.28.6](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.4...0.28.6)
- [github.com/rhysd/actionlint: v1.7.0 → v1.7.1](https://github.com/rhysd/actionlint/compare/v1.7.0...v1.7.1)
- [github.com/tox-dev/pyproject-fmt: 1.7.0 → 2.1.3](https://github.com/tox-dev/pyproject-fmt/compare/1.7.0...2.1.3)
2024-07-01 17:24:06 +00:00
Hugo van Kemenade de737fb4ee
Update config (#457) 2024-05-22 22:50:30 +03:00
Hugo van Kemenade 6f97f93dcc Update config 2024-05-22 22:46:47 +03:00
Hugo van Kemenade a23e1c5181
Fix expected result in test and refactor (#456) 2024-05-22 22:38:49 +03:00
Hugo van Kemenade 35b264bae4 Refactor 2024-05-22 22:29:05 +03:00
Hugo van Kemenade 353e32bd6b Fix expected result in test 2024-05-22 22:26:42 +03:00
Hugo van Kemenade 0fa96932a4
Update example_test_pylast.yaml Link in README.md (#455) 2024-05-22 22:08:13 +03:00
pre-commit-ci[bot] 90c3614d6a [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-05-22 05:10:35 +00:00
Christian McKinnon fa68aa4ae8 Update example_test_pylast.yaml Link in README.md 2024-05-22 11:48:48 +07:00
Hugo van Kemenade 8a26cf88d8
[pre-commit.ci] pre-commit autoupdate (#453) 2024-04-01 20:41:01 +03:00
pre-commit-ci[bot] 82cb504871
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.3.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.3.4)
- [github.com/psf/black-pre-commit-mirror: 24.1.1 → 24.3.0](https://github.com/psf/black-pre-commit-mirror/compare/24.1.1...24.3.0)
2024-04-01 17:16:48 +00:00
Hugo van Kemenade 9d4283a924
Update github-actions (#450) 2024-03-01 08:06:49 +02:00
Hugo van Kemenade e4d2ebc4a0
Update test.yml 2024-03-01 08:02:36 +02:00
renovate[bot] d5f1c3d3ac
Update github-actions 2024-03-01 01:13:50 +00:00
Hugo van Kemenade e737ae2f34
Add support for Python 3.13 (#448) 2024-02-05 21:35:25 +02:00
Hugo van Kemenade f4547a5821 Add Prettier to pre-commit 2024-02-05 21:27:22 +02:00
Hugo van Kemenade a14a50a333 Scrutinizer was removed in 2019 2024-02-05 21:27:22 +02:00
Hugo van Kemenade 77d1b0009c Add support for Python 3.13 2024-02-05 21:27:22 +02:00
Hugo van Kemenade d505d57fc4
Replace Flake8 with Ruff (#447) 2024-02-05 20:53:51 +02:00
Hugo van Kemenade 3890cb4c04 Add {envpython} and --cov-report html, multiline for clarity 2024-02-04 22:09:24 +02:00
Hugo van Kemenade 5bccda1102 Update config 2024-02-04 22:06:20 +02:00
Hugo van Kemenade 36d89a69e8 Replace Flake8 with Ruff 2024-02-04 22:06:20 +02:00
Hugo van Kemenade a28dea1158
Update github-actions (#446) 2024-02-04 21:10:29 +02:00
Hugo van Kemenade 6c888343c8
Pin codecov/codecov-action to v3.1.5 2024-02-04 12:00:33 -07:00
renovate[bot] ffebde28e2
Update github-actions 2024-02-04 18:59:28 +00:00
Hugo van Kemenade befb5aeceb
Double read timeout to fix 'The read operation timed out' (#445) 2024-02-04 20:58:44 +02:00
Hugo van Kemenade 370ff77f21 Double read timeout to fix 'The read operation timed out'
5 seconds is the default
2024-01-22 09:23:41 +02:00
Hugo van Kemenade cdfc23b5e4
Update github-actions (#443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 19:24:32 +02:00
pre-commit-ci[bot] d5fe263c23
[pre-commit.ci] pre-commit autoupdate (#444)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2024-01-01 10:31:29 -07:00
renovate[bot] e90d717b66
Update github-actions 2024-01-01 01:18:15 +00:00
Eugene Simonov b4c8dc7282
Add type annotations to methods that take timestamp parameter (#442)
Co-authored-by: Eugene Simonov <eugene.simonov@evergen.com.au>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2023-12-29 12:00:10 -07:00
Hugo van Kemenade 6f30559a3a
Fix incorrect docstrings (#439)
Changes proposed in this pull request:
- fix docstrings with inaccurate descriptions

While going through the pyLast documentation, I noticed that a few
functions had inaccurate descriptions. The proper use of these functions
should be intuitive from their names, but I thought it still might be
useful to fix their docstrings. Let me know if there are any issues with
these changes. Thanks!
2023-10-27 07:00:57 +03:00
Mia Bilka a91bac007d Fixed incorrect docstrings 2023-10-26 18:02:01 -07:00
pre-commit-ci[bot] c0f9f4222a
[pre-commit.ci] pre-commit autoupdate (#437)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-02 14:16:48 -06:00
renovate[bot] 47872dbb32
Update actions/checkout action to v4 (#436)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://togithub.com/actions/checkout) | action |
major | `v3` -> `v4` |

---

### Release Notes

<details>
<summary>actions/checkout (actions/checkout)</summary>

###
[`v4`](https://togithub.com/actions/checkout/blob/HEAD/CHANGELOG.md#v400)

[Compare Source](https://togithub.com/actions/checkout/compare/v3...v4)

- [Support fetching without the --progress
option](https://togithub.com/actions/checkout/pull/1067)
-   [Update to node20](https://togithub.com/actions/checkout/pull/1436)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "on the first day of the month" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/pylast/pylast).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4wLjMiLCJ1cGRhdGVkSW5WZXIiOiIzNy4wLjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-01 08:37:46 +03:00
pre-commit-ci[bot] 74392c4d71
[pre-commit.ci] pre-commit autoupdate (#434)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2023-08-22 14:22:24 +03:00
Hugo van Kemenade 97eab1719f
Remove default 'cache-dependency-path: pyproject.toml' (#435) 2023-07-24 17:36:32 +03:00
Hugo van Kemenade e4b7af41f9 Remove default 'cache-dependency-path: pyproject.toml'
Committed via https://github.com/asottile/all-repos
2023-07-24 17:33:42 +03:00
Hugo van Kemenade 7f1c90cfea
CI: Replace pypy3.9 with pypy3.10 (#433) 2023-06-18 14:45:26 +03:00
Hugo van Kemenade 68c0197028 CI: Replace pypy3.9 with pypy3.10
Committed via https://github.com/asottile/all-repos
2023-06-18 14:42:29 +03:00
Hugo van Kemenade 9e62e37b1e
Update mheap/github-action-required-labels action to v5 (#432) 2023-06-08 21:24:55 +03:00
Hugo van Kemenade c26c5f86aa Update on the first day of the month 2023-06-08 21:22:05 +03:00
renovate[bot] f7a73aa62f
Update mheap/github-action-required-labels action to v5 2023-06-08 17:29:12 +00:00
Hugo van Kemenade 34e0e54fea
Merge pull request #431 from pylast/deploy 2023-06-06 19:24:16 +03:00
Hugo van Kemenade 02da99f4b0 Use hynek/build-and-inspect-python-package 2023-06-06 19:20:56 +03:00
Hugo van Kemenade 5f302e0813
Merge pull request #430 from pylast/rm-3.7 2023-06-06 19:15:22 +03:00
Hugo van Kemenade 47eda3ea70 Refactor: make helper methods static 2023-06-03 18:10:01 +03:00
Hugo van Kemenade 7da76f49bd Refactor: replace redundant helper methods, no need with pytest 2023-06-03 18:03:05 +03:00
Hugo van Kemenade 1c669d8bb0 Fix test: now returns a png 2023-06-03 17:55:00 +03:00
Hugo van Kemenade 0f59831dc2 Drop support for EOL Python 3.7 2023-06-03 17:41:35 +03:00
Hugo van Kemenade 8d8263ce42
Merge pull request #428 from pylast/deploy 2023-04-18 06:31:29 -06:00
Hugo van Kemenade 07ce433fc0
Merge pull request #427 from pylast/add-3.12 2023-04-18 06:21:36 -06:00
Hugo van Kemenade dc4bd8474c Test newest PyPy 2023-04-18 06:19:54 -06:00
Hugo van Kemenade b05b8454f5 Update pre-commit 2023-04-18 06:09:25 -06:00
Hugo van Kemenade 56fc297371 Publish to PyPI with a Trusted Publisher 2023-04-18 06:08:52 -06:00
Hugo van Kemenade 9f59dd770c Add support for Python 3.12 2023-04-18 06:04:33 -06:00
Hugo van Kemenade f0ea480334
Merge pull request #426 from pylast/pre-commit-ci-update-config 2023-04-18 05:49:37 -06:00
Hugo van Kemenade 165e4761f4
Merge pull request #424 from pylast/all-repos_autofix_fix-deprecated-repository_url
Replace deprecated repository_url with repository-url
2023-04-18 05:34:12 -06:00
Hugo van Kemenade cdb88b9bbb Update pre-commit 2023-04-18 05:28:30 -06:00
pre-commit-ci[bot] 879591e1cc [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2023-04-03 19:10:56 +00:00
pre-commit-ci[bot] 6a7a23cd9a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.12.0 → 23.3.0](https://github.com/psf/black/compare/22.12.0...23.3.0)
- [github.com/asottile/blacken-docs: v1.12.1 → 1.13.0](https://github.com/asottile/blacken-docs/compare/v1.12.1...1.13.0)
- [github.com/pre-commit/pygrep-hooks: v1.9.0 → v1.10.0](https://github.com/pre-commit/pygrep-hooks/compare/v1.9.0...v1.10.0)
- [github.com/tox-dev/pyproject-fmt: 0.4.1 → 0.9.2](https://github.com/tox-dev/pyproject-fmt/compare/0.4.1...0.9.2)
- [github.com/abravalheri/validate-pyproject: v0.10.1 → v0.12.2](https://github.com/abravalheri/validate-pyproject/compare/v0.10.1...v0.12.2)
- [github.com/tox-dev/tox-ini-fmt: 0.5.2 → 1.0.0](https://github.com/tox-dev/tox-ini-fmt/compare/0.5.2...1.0.0)
2023-04-03 19:10:46 +00:00
Hugo van Kemenade 793ae1453c
Merge pull request #425 from pylast/renovate/github-actions
chore(deps): update mheap/github-action-required-labels action to v4
2023-04-02 14:03:18 +03:00
renovate[bot] 111334328e
chore(deps): update mheap/github-action-required-labels action to v4 2023-04-02 09:06:18 +00:00
Hugo van Kemenade 15f0ccfd58 Replace deprecated repository_url with repository-url
Committed via https://github.com/asottile/all-repos
2023-03-19 15:53:12 +02:00
Hugo van Kemenade 94432d62b0
Merge pull request #423 from pylast/all-repos_autofix_all-repos-sed 2023-01-29 13:29:31 +02:00
Hugo van Kemenade dab0a5b661 Bump isort to fix Poetry
Re: https://github.com/PyCQA/isort/pull/2078

Committed via https://github.com/asottile/all-repos
2023-01-29 13:27:16 +02:00
Hugo van Kemenade 7f07babdf4
Merge pull request #421 from ndm13/patch-1
Document how to authenticate with a session key
2023-01-06 09:54:32 +02:00
Hugo van Kemenade f5ea06c6c9
Include "import pylast" in both blocks 2023-01-06 09:49:52 +02:00
Hugo van Kemenade 4ea1df0930
Merge pull request #420 from pylast/pre-commit-ci-update-config 2023-01-05 18:46:19 +02:00
Hugo van Kemenade e63ecc7bea Autolabel pre-commit PRs with 'changelog: skip' 2023-01-05 18:42:43 +02:00
Hugo van Kemenade 28403386a8 Bump Black 2023-01-05 18:41:16 +02:00
Hugo van Kemenade 8647cbdd48 Make alternative clearer via own code blocks 2023-01-05 18:36:38 +02:00
ndm13 a37ac22e6c
Add code from pylast #407 to readme
Describe authentication with OAuth token
2023-01-02 15:09:16 -05:00
pre-commit-ci[bot] 7861fd55bd
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.1.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.3.1)
- [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0)
- [github.com/PyCQA/isort: 5.10.1 → 5.11.4](https://github.com/PyCQA/isort/compare/5.10.1...5.11.4)
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)
- [github.com/tox-dev/pyproject-fmt: 0.3.5 → 0.4.1](https://github.com/tox-dev/pyproject-fmt/compare/0.3.5...0.4.1)
2023-01-02 18:09:58 +00:00
Hugo van Kemenade 219be9f61a
Merge pull request #419 from pylast/renovate/github-actions 2022-12-31 00:07:48 +02:00
renovate[bot] 8169fad09d
chore(deps): update mheap/github-action-required-labels action to v3 2022-12-30 21:56:31 +00:00
Hugo van Kemenade 0152d98b28
Merge pull request #418 from pylast/all-repos_autofix_add-3.12-dev 2022-11-09 14:00:31 +02:00
Hugo van Kemenade d03e25fc6c Test Python 3.12-dev
Committed via https://github.com/asottile/all-repos
2022-11-09 13:44:07 +02:00
30 changed files with 468 additions and 321 deletions

View file

@ -1,2 +0,0 @@
[flake8]
max_line_length = 88

View file

@ -22,6 +22,11 @@ categories:
exclude-labels:
- "changelog: skip"
autolabeler:
- label: "changelog: skip"
branch:
- "/pre-commit-ci-update-config/"
template: |
$CHANGES

13
.github/renovate.json vendored
View file

@ -1,16 +1,13 @@
{
"extends": [
"config:base"
],
"labels": [
"changelog: skip",
"dependencies"
],
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"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

@ -2,50 +2,74 @@ name: Deploy
on:
push:
branches:
- main
branches: [main]
tags: ["*"]
pull_request:
branches: [main]
release:
types:
- published
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
if: github.repository_owner == 'pylast'
# Always build & lint package.
build-package:
name: Build & verify package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
- uses: hynek/build-and-inspect-python-package@v2
# 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'
runs-on: ubuntu-latest
needs: build-package
permissions:
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: pyproject.toml
name: Packages
path: dist
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U build twine wheel
- name: Build package
run: |
python -m build
twine check --strict dist/*
- name: Publish package to PyPI
if: github.event.action == 'published'
- name: Upload package to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
repository-url: https://test.pypi.org/legacy/
- name: Publish package to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
# 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'
runs-on: ubuntu-latest
needs: build-package
permissions:
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
with:
user: __token__
password: ${{ secrets.test_pypi_password }}
repository_url: https://test.pypi.org/legacy/
name: Packages
path: dist
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View file

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

View file

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

View file

@ -5,14 +5,30 @@ on:
# branches to consider in the event; optional, defaults to all
branches:
- main
# pull_request event is required only for autolabeler
pull_request:
# Only following types are handled by the action, but one can default to all as well
types: [opened, reopened, synchronize]
# pull_request_target event is required for autolabeler to support PRs from forks
# pull_request_target:
# types: [opened, reopened, synchronize]
workflow_dispatch:
permissions:
contents: read
jobs:
update_release_draft:
if: github.repository_owner == 'pylast'
permissions:
# write permission is required to create a GitHub Release
contents: write
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
runs-on: ubuntu-latest
steps:
# Drafts your next release notes as pull requests are merged into "main"
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -8,8 +8,12 @@ jobs:
label:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: mheap/github-action-required-labels@v2
- uses: mheap/github-action-required-labels@v5
with:
mode: minimum
count: 1

View file

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

View file

@ -1,63 +1,74 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.1.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- id: ruff
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 22.10.0
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
hooks:
- id: black
args: [--target-version=py37]
- repo: https://github.com/asottile/blacken-docs
rev: v1.12.1
rev: 1.18.0
hooks:
- id: blacken-docs
args: [--target-version=py37]
additional_dependencies: [black==22.10.0]
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
hooks:
- id: python-check-blanket-noqa
args: [--target-version=py38]
additional_dependencies: [black]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.6.0
hooks:
- id: check-json
- 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: requirements-txt-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
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 0.3.5
rev: 2.1.3
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
rev: v0.18
hooks:
- id: validate-pyproject
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 0.5.2
rev: 1.3.1
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

View file

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

View file

@ -12,117 +12,125 @@ 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,49 +15,44 @@ 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://github.com/pylast/pylast
python3 -m pip install -U git+https://git.hirad.it/Hirad/pylast
```
Or from requirements.txt:
```txt
-e https://github.com/pylast/pylast.git#egg=pylast
-e https://git.hirad.it/Hirad/pylast#egg=pylast
```
Note:
* pyLast 5.1+ supports Python 3.7-3.11.
* pyLast 5.0+ supports Python 3.7-3.10.
* pyLast 4.3+ 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.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.
## 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
@ -84,7 +79,43 @@ network = pylast.LastFMNetwork(
username=username,
password_hash=password_hash,
)
```
Alternatively, instead of creating `network` with a username and password, you can
authenticate with a session key:
```python
import pylast
SESSION_KEY_FILE = os.path.join(os.path.expanduser("~"), ".session_key")
network = pylast.LastFMNetwork(API_KEY, API_SECRET)
if not os.path.exists(SESSION_KEY_FILE):
skg = pylast.SessionKeyGenerator(network)
url = skg.get_web_auth_url()
print(f"Please authorize this script to access your account: {url}\n")
import time
import webbrowser
webbrowser.open(url)
while True:
try:
session_key = skg.get_web_auth_session_key(url)
with open(SESSION_KEY_FILE, "w") as f:
f.write(session_key)
break
except pylast.WSError:
time.sleep(1)
else:
session_key = open(SESSION_KEY_FILE).read()
network.session_key = session_key
```
And away we go:
```python
# Now you can use that object everywhere
track = network.get_track("Iron Maiden", "The Nomad")
track.love()
@ -105,8 +136,9 @@ 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](example_test_pylast.yaml) to test_pylast.yaml and fill out
the credentials, or set them as environment variables like:
[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:
```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,7 +12,8 @@
- [ ] 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,47 +15,46 @@ 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"}]
requires-python = ">=3.7"
dependencies = [
"httpx",
'importlib-metadata; python_version < "3.8"',
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 :: 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",
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dynamic = [
"version",
]
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.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet",
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Software Development :: Libraries :: Python Modules",
dependencies = [
"httpx",
]
[project.optional-dependencies]
tests = [
optional-dependencies.tests = [
"flaky",
"pytest",
"pytest-cov",
"pytest-random-order",
"pyyaml",
]
[project.urls]
Changelog = "https://github.com/pylast/pylast/releases"
Homepage = "https://github.com/pylast/pylast"
Source = "https://github.com/pylast/pylast"
urls.Changelog = "https://github.com/pylast/pylast/releases"
urls.Homepage = "https://github.com/pylast/pylast"
urls.Source = "https://github.com/pylast/pylast"
[tool.hatch]
version.source = "vcs"
@ -63,5 +62,36 @@ version.source = "vcs"
[tool.hatch.version.raw-options]
local_scheme = "no-local-version"
[tool.isort]
profile = "black"
[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"

View file

@ -1,6 +1,6 @@
#
# pylast -
# A Python interface to Last.fm and Libre.fm
# A Python interface to Last.fm and music.lonestar.it
#
# Copyright 2008-2010 Amr Hassan
# Copyright 2013-2021 hugovk
@ -23,6 +23,7 @@ from __future__ import annotations
import collections
import hashlib
import html.entities
import importlib.metadata
import logging
import os
import re
@ -36,18 +37,11 @@ from xml.dom import Node, minidom
import httpx
try:
# Python 3.8+
import importlib.metadata as importlib_metadata
except ImportError:
# Python 3.7 and lower
import importlib_metadata # type: ignore
__author__ = "Amr Hassan, hugovk, Mice Pápai"
__copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2021 hugovk, 2017 Mice Pápai"
__license__ = "apache2"
__email__ = "amr.hassan@gmail.com"
__version__ = importlib_metadata.version(__name__)
__version__ = importlib.metadata.version(__name__)
# 1 : This error does not exist
@ -535,26 +529,25 @@ class _Network:
def scrobble(
self,
artist,
title,
timestamp,
album=None,
album_artist=None,
track_number=None,
duration=None,
stream_id=None,
context=None,
mbid=None,
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,
):
"""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). This must be in the UTC time zone.
January 1st 1970 UTC).
album (Optional) : The album name.
album_artist (Optional) : The album artist - if this differs from
the track artist.
@ -600,7 +593,6 @@ class _Network:
params = {}
for i in range(len(tracks_to_scrobble)):
params[f"artist[{i}]"] = tracks_to_scrobble[i]["artist"]
params[f"track[{i}]"] = tracks_to_scrobble[i]["title"]
@ -621,7 +613,6 @@ class _Network:
}
for arg in additional_args:
if arg in tracks_to_scrobble[i] and tracks_to_scrobble[i][arg]:
if arg in args_map_to:
maps_to = args_map_to[arg]
@ -637,7 +628,6 @@ class _Network:
class LastFMNetwork(_Network):
"""A Last.fm network object
api_key: a provided API_KEY
@ -715,7 +705,7 @@ class LastFMNetwork(_Network):
class LibreFMNetwork(_Network):
"""
A preconfigured _Network object for Libre.fm
A preconfigured _Network object for music.lonestar.it
api_key: a provided API_KEY
api_secret: a provided API_SECRET
@ -736,29 +726,28 @@ class LibreFMNetwork(_Network):
username: str = "",
password_hash: str = "",
) -> None:
super().__init__(
name="Libre.fm",
homepage="https://libre.fm",
ws_server=("libre.fm", "/2.0/"),
name="music.lonestar.it",
homepage="https://music.lonestar.it",
ws_server=("music.lonestar.it", "/2.0/"),
api_key=api_key,
api_secret=api_secret,
session_key=session_key,
username=username,
password_hash=password_hash,
domain_names={
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",
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",
},
urls={
"album": "artist/%(artist)s/album/%(album)s",
@ -904,6 +893,7 @@ 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(
@ -911,12 +901,14 @@ 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:
@ -1166,7 +1158,7 @@ class _BaseObject:
def get_wiki_published_date(self):
"""
Returns the summary of the wiki.
Returns the date on which the wiki was published.
Only for Album/Track.
"""
return self.get_wiki("published")
@ -1180,7 +1172,7 @@ class _BaseObject:
def get_wiki_content(self):
"""
Returns the summary of the wiki.
Returns the content of the wiki.
Only for Album/Track.
"""
return self.get_wiki("content")
@ -1250,8 +1242,10 @@ 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 = eval(chart_kind.title()) # string to type
chart_type = getattr(sys.modules[__name__], chart_kind.title())
params = self._get_params()
if from_date and to_date:
@ -1363,11 +1357,11 @@ class _Taggable(_BaseObject):
new_tags.append(tag)
for i in range(0, len(old_tags)):
if not c_old_tags[i] in c_new_tags:
if c_old_tags[i] not in c_new_tags:
to_remove.append(old_tags[i])
for i in range(0, len(new_tags)):
if not c_new_tags[i] in c_old_tags:
if c_new_tags[i] not in c_old_tags:
to_add.append(new_tags[i])
self.remove_tags(to_remove)
@ -1515,7 +1509,7 @@ class _Opus(_Taggable):
return f"{self.get_artist().get_name()} - {self.get_title()}"
def __eq__(self, other):
if type(self) != type(other):
if type(self) is not type(other):
return False
a = self.get_title().lower()
b = other.get_title().lower()
@ -1553,7 +1547,7 @@ class _Opus(_Taggable):
return self.info["image"][size]
def get_title(self, properly_capitalized: bool = False):
"""Returns the artist or track title."""
"""Returns the album or track title."""
if properly_capitalized:
self.title = _extract(
self._request(self.ws_prefix + ".getInfo", True), "name"
@ -2305,8 +2299,8 @@ class User(_Chartable):
self,
limit: int = 10,
cacheable: bool = True,
time_from=None,
time_to=None,
time_from: int | None = None,
time_to: int | None = None,
stream: bool = False,
now_playing: bool = False,
):
@ -2317,13 +2311,11 @@ 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). This
must be in the UTC time zone.
scrobbles after this time, in Unix timestamp format (integer
number of seconds since 00:00:00, January 1st 1970 UTC).
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). This must be in
the UTC time zone.
before this time, in Unix timestamp format (integer number of
seconds since 00:00:00, January 1st 1970 UTC).
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
@ -2392,7 +2384,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)
@ -2787,7 +2779,8 @@ def _collect_nodes(
main.getAttribute("totalPages") or main.getAttribute("totalpages")
)
else:
raise PyLastError("No total pages attribute")
msg = "No total pages attribute"
raise PyLastError(msg)
for node in main.childNodes:
if not node.nodeType == xml.dom.Node.TEXT_NODE and (

View file

@ -2,6 +2,8 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm
@ -94,8 +96,8 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
image = album.get_cover_image()
# Assert
self.assert_startswith(image, "https://")
self.assert_endswith(image, ".gif")
assert image.startswith("https://")
assert image.endswith(".gif") or image.endswith(".png")
def test_mbid(self) -> None:
# Arrange

View file

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

View file

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

View file

@ -2,6 +2,8 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import pylast
from .test_pylast import TestPyLastWithLastFm
@ -16,7 +18,7 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
representation = repr(library)
# Assert
self.assert_startswith(representation, "pylast.Library(")
assert representation.startswith("pylast.Library(")
def test_str(self) -> None:
# Arrange
@ -26,7 +28,7 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
string = str(library)
# Assert
self.assert_endswith(string, "'s Library")
assert string.endswith("'s Library")
def test_library_is_hashable(self) -> None:
# Arrange

View file

@ -2,15 +2,17 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
from flaky import flaky
import pylast
from .test_pylast import PyLastTestCase, load_secrets
from .test_pylast import load_secrets
@flaky(max_runs=3, min_passes=1)
class TestPyLastWithLibreFm(PyLastTestCase):
class TestPyLastWithLibreFm:
"""Own class for Libre.fm because we don't need the Last.fm setUp"""
def test_libre_fm(self) -> None:
@ -38,4 +40,4 @@ class TestPyLastWithLibreFm(PyLastTestCase):
representation = repr(network)
# Assert
self.assert_startswith(representation, "pylast.LibreFMNetwork(")
assert representation.startswith("pylast.LibreFMNetwork(")

View file

@ -1,6 +1,9 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import re
import time
@ -330,12 +333,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert
assert len(images) == 4
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
assert images[pylast.SIZE_SMALL].startswith("https://")
assert images[pylast.SIZE_SMALL].endswith(".png")
assert "/34s/" in images[pylast.SIZE_SMALL]
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
def test_artist_search(self) -> None:
@ -362,12 +365,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert
assert len(images) == 5
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
assert images[pylast.SIZE_SMALL].startswith("https://")
assert images[pylast.SIZE_SMALL].endswith(".png")
assert "/34s/" in images[pylast.SIZE_SMALL]
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
def test_track_search(self) -> None:
@ -396,12 +399,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert
assert len(images) == 4
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
assert images[pylast.SIZE_SMALL].startswith("https://")
assert images[pylast.SIZE_SMALL].endswith(".png")
assert "/34s/" in images[pylast.SIZE_SMALL]
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
def test_search_get_total_result_count(self) -> None:

View file

@ -2,6 +2,8 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import os
import time
@ -32,25 +34,17 @@ def load_secrets(): # pragma: no cover
return doc
class PyLastTestCase:
def assert_startswith(self, s, prefix, start=None, end=None) -> None:
assert s.startswith(prefix, start, end)
def assert_endswith(self, s, suffix, start=None, end=None) -> None:
assert s.endswith(suffix, start, end)
def _no_xfail_rerun_filter(err, name, test, plugin) -> bool:
for _ in test.iter_markers(name="xfail"):
return False
@flaky(max_runs=3, min_passes=1, rerun_filter=_no_xfail_rerun_filter)
class TestPyLastWithLastFm(PyLastTestCase):
class TestPyLastWithLastFm:
secrets = None
def unix_timestamp(self):
@staticmethod
def unix_timestamp() -> int:
return int(time.time())
@classmethod
@ -71,7 +65,8 @@ class TestPyLastWithLastFm(PyLastTestCase):
password_hash=password_hash,
)
def helper_is_thing_hashable(self, thing) -> None:
@staticmethod
def helper_is_thing_hashable(thing) -> None:
# Arrange
things = set()
@ -82,7 +77,8 @@ class TestPyLastWithLastFm(PyLastTestCase):
assert thing is not None
assert len(things) == 1
def helper_validate_results(self, a, b, c) -> None:
@staticmethod
def helper_validate_results(a, b, c) -> None:
# Assert
assert a is not None
assert b is not None
@ -106,27 +102,31 @@ class TestPyLastWithLastFm(PyLastTestCase):
# Assert
self.helper_validate_results(result1, result2, result3)
def helper_at_least_one_thing_in_top_list(self, things, expected_type) -> None:
@staticmethod
def helper_at_least_one_thing_in_top_list(things, expected_type) -> None:
# Assert
assert len(things) > 1
assert isinstance(things, list)
assert isinstance(things[0], pylast.TopItem)
assert isinstance(things[0].item, expected_type)
def helper_only_one_thing_in_top_list(self, things, expected_type) -> None:
@staticmethod
def helper_only_one_thing_in_top_list(things, expected_type) -> None:
# Assert
assert len(things) == 1
assert isinstance(things, list)
assert isinstance(things[0], pylast.TopItem)
assert isinstance(things[0].item, expected_type)
def helper_only_one_thing_in_list(self, things, expected_type) -> None:
@staticmethod
def helper_only_one_thing_in_list(things, expected_type) -> None:
# Assert
assert len(things) == 1
assert isinstance(things, list)
assert isinstance(things[0], expected_type)
def helper_two_different_things_in_top_list(self, things, expected_type) -> None:
@staticmethod
def helper_two_different_things_in_top_list(things, expected_type) -> None:
# Assert
assert len(things) == 2
thing1 = things[0]

View file

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

View file

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

View file

@ -2,6 +2,8 @@
"""
Integration (not unit) tests for pylast.py
"""
from __future__ import annotations
import calendar
import datetime as dt
import inspect
@ -24,7 +26,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
representation = repr(user)
# Assert
self.assert_startswith(representation, "pylast.User('RJ',")
assert representation.startswith("pylast.User('RJ',")
def test_str(self) -> None:
# Arrange
@ -345,7 +347,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
url = user.get_image()
# Assert
self.assert_startswith(url, "https://")
assert url.startswith("https://")
def test_user_get_library(self) -> None:
# Arrange
@ -428,8 +430,8 @@ class TestPyLastUser(TestPyLastWithLastFm):
image = user.get_image()
# Assert
self.assert_startswith(image, "https://")
self.assert_endswith(image, ".png")
assert image.startswith("https://")
assert image.endswith(".png")
def test_get_url(self) -> None:
# Arrange

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from unittest import mock
import pytest
@ -25,7 +27,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 type(str(obj)) is str
assert isinstance(str(obj), str)
assert isinstance(hash(obj), int)

26
tox.ini
View file

@ -1,27 +1,35 @@
[tox]
envlist =
requires =
tox>=4.2
env_list =
lint
py{py3, 311, 310, 39, 38, 37}
isolated_build = true
py{py3, 313, 312, 311, 310, 39, 38}
[testenv]
passenv =
extras =
tests
pass_env =
FORCE_COLOR
PYLAST_API_KEY
PYLAST_API_SECRET
PYLAST_PASSWORD_HASH
PYLAST_USERNAME
extras =
tests
commands =
pytest -v -s -W all --cov pylast --cov tests --cov-report term-missing --cov-report xml --random-order {posargs}
{envpython} -m pytest -v -s -W all \
--cov pylast \
--cov tests \
--cov-report html \
--cov-report term-missing \
--cov-report xml \
--random-order \
{posargs}
[testenv:lint]
passenv =
PRE_COMMIT_COLOR
skip_install = true
deps =
pre-commit
pass_env =
PRE_COMMIT_COLOR
commands =
pre-commit run --all-files --show-diff-on-failure