ampachedata 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ampachedata/__init__.py +59 -0
- ampachedata/__main__.py +136 -0
- ampachedata/data/AmpacheClient.py +805 -0
- ampachedata/data/ApiMethod.py +27 -0
- ampachedata/data/Bootstrap.py +65 -0
- ampachedata/data/ErrorCode.py +18 -0
- ampachedata/data/ObjectType.py +14 -0
- ampachedata/data/StatsFilter.py +20 -0
- ampachedata/data/Transport.py +77 -0
- ampachedata/data/__init__.py +4 -0
- ampachedata/data/auth/Handshake.py +15 -0
- ampachedata/data/auth/SessionManager.py +99 -0
- ampachedata/data/auth/__init__.py +3 -0
- ampachedata/data/db/Database.py +25 -0
- ampachedata/data/db/__init__.py +3 -0
- ampachedata/data/db/mappers/AlbumMapper.py +41 -0
- ampachedata/data/db/mappers/ArtistMapper.py +32 -0
- ampachedata/data/db/mappers/GenreMapper.py +3 -0
- ampachedata/data/db/mappers/HistoryMapper.py +40 -0
- ampachedata/data/db/mappers/PlaylistMapper.py +32 -0
- ampachedata/data/db/mappers/PlaylistSongMapper.py +22 -0
- ampachedata/data/db/mappers/SessionMapper.py +46 -0
- ampachedata/data/db/mappers/SongMapper.py +77 -0
- ampachedata/data/db/mappers/UserMapper.py +3 -0
- ampachedata/data/db/mappers/__init__.py +3 -0
- ampachedata/data/db/repositories/AlbumRepository.py +115 -0
- ampachedata/data/db/repositories/ArtistRepository.py +98 -0
- ampachedata/data/db/repositories/CredentialsRepository.py +43 -0
- ampachedata/data/db/repositories/GenreRepository.py +3 -0
- ampachedata/data/db/repositories/HistoryRepository.py +51 -0
- ampachedata/data/db/repositories/LikePattern.py +21 -0
- ampachedata/data/db/repositories/PlaylistRepository.py +97 -0
- ampachedata/data/db/repositories/PlaylistSongRepository.py +32 -0
- ampachedata/data/db/repositories/SessionRepository.py +56 -0
- ampachedata/data/db/repositories/SongRepository.py +273 -0
- ampachedata/data/db/repositories/UserRepository.py +3 -0
- ampachedata/data/db/repositories/__init__.py +3 -0
- ampachedata/data/errors.py +86 -0
- ampachedata/domain/Album.py +24 -0
- ampachedata/domain/Artist.py +20 -0
- ampachedata/domain/Credentials.py +13 -0
- ampachedata/domain/Genre.py +3 -0
- ampachedata/domain/History.py +13 -0
- ampachedata/domain/OperationResult.py +15 -0
- ampachedata/domain/PageResult.py +20 -0
- ampachedata/domain/PingResult.py +15 -0
- ampachedata/domain/Playlist.py +19 -0
- ampachedata/domain/PlaylistSong.py +14 -0
- ampachedata/domain/Session.py +13 -0
- ampachedata/domain/Song.py +58 -0
- ampachedata/domain/User.py +3 -0
- ampachedata/domain/__init__.py +3 -0
- ampachedata-0.1.0.dist-info/METADATA +85 -0
- ampachedata-0.1.0.dist-info/RECORD +57 -0
- ampachedata-0.1.0.dist-info/WHEEL +5 -0
- ampachedata-0.1.0.dist-info/licenses/LICENSE +674 -0
- ampachedata-0.1.0.dist-info/top_level.txt +1 -0
ampachedata/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 icefields
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
3
|
+
"""ampachedata — public API. Anything not re-exported here is private."""
|
|
4
|
+
from .data.AmpacheClient import AmpacheClient
|
|
5
|
+
from .data.Bootstrap import storeCredentialsFromKey, storeCredentialsFromPassword
|
|
6
|
+
from .data.ObjectType import ObjectType
|
|
7
|
+
from .data.errors import (
|
|
8
|
+
AccessDeniedError,
|
|
9
|
+
AmpacheError,
|
|
10
|
+
ApiError,
|
|
11
|
+
BadRequestError,
|
|
12
|
+
CacheVerificationError,
|
|
13
|
+
CredentialValidationError,
|
|
14
|
+
DatabaseError,
|
|
15
|
+
DeprecatedError,
|
|
16
|
+
InvalidHandshakeError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
UnknownApiError,
|
|
19
|
+
)
|
|
20
|
+
from .domain.Artist import Artist
|
|
21
|
+
from .domain.Album import Album
|
|
22
|
+
from .domain.Credentials import Credentials
|
|
23
|
+
from .domain.History import History
|
|
24
|
+
from .domain.OperationResult import OperationResult
|
|
25
|
+
from .domain.PageResult import PageResult
|
|
26
|
+
from .domain.PingResult import PingResult
|
|
27
|
+
from .domain.Playlist import Playlist
|
|
28
|
+
from .domain.PlaylistSong import PlaylistSong
|
|
29
|
+
from .domain.Session import Session
|
|
30
|
+
from .domain.Song import Song
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"AmpacheClient",
|
|
34
|
+
"Artist",
|
|
35
|
+
"Album",
|
|
36
|
+
"Song",
|
|
37
|
+
"Playlist",
|
|
38
|
+
"PlaylistSong",
|
|
39
|
+
"History",
|
|
40
|
+
"Session",
|
|
41
|
+
"Credentials",
|
|
42
|
+
"PingResult",
|
|
43
|
+
"OperationResult",
|
|
44
|
+
"PageResult",
|
|
45
|
+
"ObjectType",
|
|
46
|
+
"storeCredentialsFromPassword",
|
|
47
|
+
"storeCredentialsFromKey",
|
|
48
|
+
"AmpacheError",
|
|
49
|
+
"ApiError",
|
|
50
|
+
"DatabaseError",
|
|
51
|
+
"InvalidHandshakeError",
|
|
52
|
+
"AccessDeniedError",
|
|
53
|
+
"NotFoundError",
|
|
54
|
+
"DeprecatedError",
|
|
55
|
+
"BadRequestError",
|
|
56
|
+
"CredentialValidationError",
|
|
57
|
+
"CacheVerificationError",
|
|
58
|
+
"UnknownApiError",
|
|
59
|
+
]
|
ampachedata/__main__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 icefields
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
3
|
+
"""CLI: python -m ampachedata init-credentials ...
|
|
4
|
+
|
|
5
|
+
First-run credentials bootstrap. The cleartext password is accepted ONCE here —
|
|
6
|
+
via hidden getpass prompt, stdin, or an environment variable — hashed with SHA256
|
|
7
|
+
in memory, and only the digest is written to the single CredentialsEntity row.
|
|
8
|
+
There is deliberately no --password flag: cleartext never appears in argv, shell
|
|
9
|
+
history, or ps. Nothing else is touched: no handshake, no SessionEntity write."""
|
|
10
|
+
import argparse
|
|
11
|
+
import getpass
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from .data.Bootstrap import storeCredentialsFromKey, storeCredentialsFromPassword
|
|
16
|
+
from .data.errors import AmpacheError, CredentialValidationError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> int:
|
|
20
|
+
args = _buildParser().parse_args(argv)
|
|
21
|
+
try:
|
|
22
|
+
return _runInitCredentials(args)
|
|
23
|
+
except (EOFError, KeyboardInterrupt):
|
|
24
|
+
print("aborted", file=sys.stderr)
|
|
25
|
+
return 130
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _buildParser():
|
|
29
|
+
parser = argparse.ArgumentParser(prog="python -m ampachedata")
|
|
30
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
31
|
+
init = subparsers.add_parser(
|
|
32
|
+
"init-credentials",
|
|
33
|
+
help="first-run bootstrap: write the single CredentialsEntity row (hash only)",
|
|
34
|
+
description=(
|
|
35
|
+
"Store the single CredentialsEntity row. The cleartext password is "
|
|
36
|
+
"accepted once — hidden prompt, stdin, or env var — hashed with "
|
|
37
|
+
"SHA256 in memory, and only the digest is stored. There is no "
|
|
38
|
+
"--password flag: cleartext never appears in argv."
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
init.add_argument("--db-path", required=True,
|
|
42
|
+
help="path to an existing musicdb.db (never created)")
|
|
43
|
+
init.add_argument("--username",
|
|
44
|
+
help="Ampache username (prompted when omitted interactively)")
|
|
45
|
+
init.add_argument("--server-url",
|
|
46
|
+
help="https://server (prompted when omitted interactively)")
|
|
47
|
+
source = init.add_mutually_exclusive_group()
|
|
48
|
+
source.add_argument("--password-stdin", action="store_true",
|
|
49
|
+
help="read the cleartext password from one stdin line")
|
|
50
|
+
source.add_argument("--password-env", metavar="VARNAME",
|
|
51
|
+
help="read the cleartext password from this environment variable")
|
|
52
|
+
source.add_argument("--key", metavar="HASH",
|
|
53
|
+
help="pre-hashed 64-hex key (visible in argv/shell history — "
|
|
54
|
+
"prefer --key-stdin)")
|
|
55
|
+
source.add_argument("--key-stdin", action="store_true",
|
|
56
|
+
help="read the pre-hashed 64-hex key from one stdin line")
|
|
57
|
+
return parser
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _runInitCredentials(args) -> int:
|
|
61
|
+
source = _secretSource(args)
|
|
62
|
+
username = args.username
|
|
63
|
+
serverUrl = args.server_url
|
|
64
|
+
|
|
65
|
+
if source is None: # interactive
|
|
66
|
+
if not sys.stdin.isatty():
|
|
67
|
+
print("error: no secret source given and stdin is not a TTY — pass "
|
|
68
|
+
"--password-stdin, --password-env VARNAME, --key, or --key-stdin",
|
|
69
|
+
file=sys.stderr)
|
|
70
|
+
return 2
|
|
71
|
+
if not username:
|
|
72
|
+
username = input("Username: ").strip()
|
|
73
|
+
if not serverUrl:
|
|
74
|
+
serverUrl = input("Server URL: ").strip()
|
|
75
|
+
password = getpass.getpass("Password: ")
|
|
76
|
+
if password != getpass.getpass("Confirm password: "):
|
|
77
|
+
print("error: passwords do not match", file=sys.stderr)
|
|
78
|
+
return 2
|
|
79
|
+
return _finish(storeCredentialsFromPassword, args.db_path, username, serverUrl, password)
|
|
80
|
+
|
|
81
|
+
if not username or not serverUrl:
|
|
82
|
+
print("error: --username and --server-url are required with " + source,
|
|
83
|
+
file=sys.stderr)
|
|
84
|
+
return 2
|
|
85
|
+
if source == "--password-stdin":
|
|
86
|
+
return _finish(storeCredentialsFromPassword, args.db_path, username, serverUrl,
|
|
87
|
+
_readSecretLine())
|
|
88
|
+
if source == "--password-env":
|
|
89
|
+
password = os.environ.get(args.password_env)
|
|
90
|
+
if password is None:
|
|
91
|
+
print("error: environment variable " + args.password_env + " is not set",
|
|
92
|
+
file=sys.stderr)
|
|
93
|
+
return 2
|
|
94
|
+
return _finish(storeCredentialsFromPassword, args.db_path, username, serverUrl, password)
|
|
95
|
+
if source == "--key":
|
|
96
|
+
return _finish(storeCredentialsFromKey, args.db_path, username, serverUrl, args.key)
|
|
97
|
+
return _finish(storeCredentialsFromKey, args.db_path, username, serverUrl,
|
|
98
|
+
_readSecretLine()) # --key-stdin
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _secretSource(args):
|
|
102
|
+
if args.password_stdin:
|
|
103
|
+
return "--password-stdin"
|
|
104
|
+
if args.password_env:
|
|
105
|
+
return "--password-env"
|
|
106
|
+
if args.key is not None:
|
|
107
|
+
return "--key"
|
|
108
|
+
if args.key_stdin:
|
|
109
|
+
return "--key-stdin"
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _readSecretLine() -> str:
|
|
114
|
+
line = sys.stdin.readline()
|
|
115
|
+
if line.endswith("\n"):
|
|
116
|
+
line = line[:-1]
|
|
117
|
+
if line.endswith("\r"):
|
|
118
|
+
line = line[:-1]
|
|
119
|
+
return line
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _finish(storeFn, dbPath, username, serverUrl, secret) -> int:
|
|
123
|
+
try:
|
|
124
|
+
storeFn(dbPath, username, serverUrl, secret)
|
|
125
|
+
except CredentialValidationError as exc:
|
|
126
|
+
print("error: " + str(exc), file=sys.stderr)
|
|
127
|
+
return 2
|
|
128
|
+
except AmpacheError as exc:
|
|
129
|
+
print("error: " + str(exc), file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
print("Stored credentials for user '" + username + "' at " + serverUrl)
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
sys.exit(main())
|