mental32 / spotify.py Goto Github PK
View Code? Open in Web Editor NEW🌐 API wrapper for Spotify 🎶
Home Page: https://spotifypy.readthedocs.io/en/latest/
License: MIT License
🌐 API wrapper for Spotify 🎶
Home Page: https://spotifypy.readthedocs.io/en/latest/
License: MIT License
Version: master
Python: 3.7.5
aiohttp (3.6.2)
I tried both modes, async and sync, same result :
2019-12-07 10:39:21 asyncio[3439466] ERROR Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x7fe47f168810> 2019-12-07 10:39:21 asyncio[3439466] ERROR Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7fe47f1514b0>, 1495824.926715675)]'] connector: <aiohttp.connector.TCPConnector object at 0x7fe47f168850>
It seems I have to manually close it : await user.http.close()
Moreover, in your README, there is :
client.loop.run_until_complete(main())
Should it be "asyncio.run(main())" ?
player.shuffle(state=True)
produces following error:
Traceback (most recent call last):
File "./test.py", line 39, in <module>
player.shuffle(state=True)
File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/sync/models.py", line 64, in wrapper
func(self, *args, **kwargs)
File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
raise err
File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/models/player.py", line 244, in shuffle
await self.user.http.shuffle_playback(state, device_id=device_id)
File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/http.py", line 188, in request
method, url, headers=headers, **kwargs
File "/home/m3nix/.local/lib/python3.7/site-packages/aiohttp/client.py", line 473, in _request
ssl=ssl, proxy_headers=proxy_headers, traces=traces)
File "/home/m3nix/.local/lib/python3.7/site-packages/aiohttp/client_reqrep.py", line 263, in __init__
url2 = url.with_query(params)
File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 885, in with_query
new_query = self._get_str_query(*args, **kwargs)
File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 849, in _get_str_query
quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items()
File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 849, in <genexpr>
quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items()
File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 827, in _query_var
"of type {}".format(v, type(v))
TypeError: Invalid variable type: value should be str or int, got True of type <class 'bool'>
using this instead works but is not the intended way I guess
player.shuffle(state="True")
Error is as the title says. Heres here traceback.
AttributeError: 'NoneType' object has no attribute 'images'
Traceback (most recent call last):
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
ret = await coro(*args, **kwargs)
File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\music.py", line 70, in play
search_result = await ctx.player.get_results(ctx=ctx, search=search)
File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\utilities\player.py", line 97, in get_results
spotify_tracks = await result.get_all_tracks()
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\album.py", line 125, in get_all_tracks
return [track async for track in self]
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\album.py", line 125, in <listcomp>
return [track async for track in self]
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\base.py", line 145, in __aiter__
yield klass(client, item) # pylint: disable=not-callable
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 78, in __init__
self.images = album.images.copy()
I think this may be an api change as only a week ago with the same album this function worked normally, Now it is broken.
Hi, @mental32 ! thanks for the fantastic work, your library is beautiful, I am using it to the fullest extent in my project)
However, there is a problem in registering the Spotify client
Calling the following code:
spotify.User.from_code (
code = code,
client = spotify_cl,
redirect_uri = self.redirect_uri,
)
I came across the following problem in the init method of the User class: /spotify/models/user.py, 88 line of code
self.url = data.poprecently ("external_urls"). get ("spotify", None)
-> AttributeError: 'dict' object has no attribute 'poprecently'
If you comment out this line, then everything is in order and everything works, please correct this moment.
Sincerely, Roman Demyanchuk!
Right, so I've investigated the issue and have a possible fix ready.
But first I'd like to address a couple points.
instead of waiting, justs tries requesting and exits with error code 439(Too many requests), when it should wait for the rate limit to pass.
The underlying http client does actually stall the request waiting for the rate limit to pass over. This issue here was that the stalling is performed only for that request and not all requests client wide resulting in a form of head of line blocking.
Additionally due to a large amount of requests made in a short period of time, which the library did not anticipate in the original design of the stalling mechanism, requests could essentially start "choking" each other out where one request finishes their rate limit stall but is then immediately rate limited again due to a sibling request taking its slot. The exception you received was a result of this following ten consecutive request failures all being rate limited.
When i do something like:
SNIP
And i get ratelimited
Unfortunately the API response does not let us know how many tries we have remaining in order to start performing client side throttling so the rate limit as a whole can be avoided. The response will only let us know if we've been rate limited or not and the library can only deal with that to the extent of waiting out the rate limit but not avoiding it.
If you do not want to hit the rate limit in the first place then you must throttle your own requests manually (this can be as simple as sleeping for a small amount of time between requests) or use multiple client applications as rate limits are applied on a per client app basis (although I do not know if this is endorsed by Spotify)
Right, so I've investigated the issue and have a possible fix ready.
I was a little reluctant to use the patch as it is right now since it feels inefficient (I performed 4096 search queries in four minutes with over 30 rate limits and I feel like that its possible to assume that either less time taken or less rate limits triggered but maybe not both) but I think its acceptable to release it under 0.8.6
temporarily and then succeed it with 0.8.7
as quickly as possible.
The fix that I've hacked together uses a barrier to stall all requests uniformly which gets controlled by the request that first gets rate limited. Unfortunately once the barrier gets lifted it's essentially a mad max free for all zerg rush for all stalled requests leading to more frequent rate limiting responses feeding a frequent stall, release, stall cycle until the amount of requests being made lowers enough to allow the API to recover from the clients madness.
Additionally that doesn't stop the possibility of choking out the entire pipeline leading to the same exception being raised, it merely delays it as much as possible. To combat this I've added a fallback stalling around the entire process, so when a request gets choked out the stalling falls back to an exponential backoff algorithm supplied by the new backoff
dependency.
Ideally the two systems would get merged into one to avoid unnecessary overhead and allow some sort of slow bleed of requests back into the http session, but I'm not sure of how to tackle this and am experimenting in the dark for the time being.
Originally posted by @mental32 in #51 (comment)
When i do something like:
for song in music_files:
tasks.append(asyncio.create_task(client.search(
cleanup_name(song.name),
types=["track"])))
done = await asyncio.gather(*tasks)
And i get ratelimited, the client, instead of waiting, justs tries requesting and exits with error code 439(Too many requests), when it should wait for the ratelimit to pass.
I am doing something similar to your "Usage with flask" example where I store User objects in a dict for an indefinite period of time. I've been using the refresh=True option to automatically refresh the token (which works) but it doesn't seem good to me that it is continuously refreshing the token forever when I'm not making any requests with the user. This is especially bad if I create multiple copies of the User object which all keep refreshing. Ultimately, I don't think I should be storing Users in a dict forever but that's another discussion which I believe mikonse's pull request is trying to address.
My thought is that it would make more sense if you handled the token expiration in HttpClient.request by catching the exception:
spotify.errors.HTTPException: Unauthorized (status code: 401): The access token expired
refreshing and retrying the request (just like you are doing for several other exceptions). I saw it done this way in some other oauth implementations too.
When I looked at the HttpClient.request code I was surprised to see you are actually handling 401 and calling self.get_bearer_info(). However, I'm not sure what that function is supposed to be doing... It doesn't take the code, token or refresh_token as input. I'm pretty sure its not working because the 401 exception just keeps happening RETRY_AMOUNT times before raising out of the loop.
When invoking .resume() on the Spotify.Player object, I recieve the following error:
File "/Users//github/SpotiTerm-Rewrite/general.py", line 157, in controls
_player.resume(device=chosen_device.id)
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/models.py", line 42, in wrapper
func(self, *args, **kwargs)
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
raise err
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/models/player.py", line 93, in resume
await self.user.http.play_playback(None, device_id=str(device))
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/http.py", line 1078, in play_playback
f"`context_uri` must be a string or an iterable object, got {type(context_uri)}"
TypeError: `context_uri` must be a string or an iterable object, got <class 'NoneType'>
Hi, I'm one of the maintainers of Mopidy and pyspotify.
After debugging with a Mopidy user recently, we found that their installation of Mopidy-Spotify, which is using pyspotify, was weirdly broken because the users also had spotify.py installed. The problem is that both projects use the spotify
name for the top-level Python package, which makes it impossible to install both projects at the same time.
As pyspotify predates spotify.py by about eight years, I would very much appreciate it if you considered renaming your top-level package to something else than spotify
, e.g. to spotifypy
.
Thanks!
When I call user.get_playlists() and user.library.get_tracks() I need all the playlists/tracks but it currently limits me to getting 50 at a time.
It would be nice if I could pass limit=None to get them all at once. I'll make a pull request if you think its worth doing.
File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/lib64/python3.7/site-packages/spotify/client.py", line 140, in oauth2_url
state=state,
File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/lib64/python3.7/site-packages/spotify/oauth.py", line 147, in url_only
oauth = OAuth2(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'scope'
Lines 125 to 126 in f42b5c5
There's a new set of endpoints dealing with podcasts, I keep forgetting to start work on support for it so hopefully opening this issue will keep me from forgetting again.
Hi Mental32, I've been trying to access my personal spotify data using your top_tracks example. I have a client set up and I understand that I need some user token (user = await client.user_from_token("sometoken"). How can I obtain this token easily, without building some ui?
Thanks for your time
If I supply or don't supply the device
parameter to Player.pause, resume, play etc. The error device not found is thrown.
It is strange as just before that, I perform user.get_devices()
and retrieve the Spotify.Device
object, which I pass directly to the parameter.
Full traceback:
File "spotiterm.py", line 54, in main
options[input(green("Select Option: "))](client, user)
File "/Users/github/SpotiTerm-Rewrite/general.py", line 181, in player
options[input(green("Select Option: "))](chosen_device, _player, user)
File "/Users//github/SpotiTerm-Rewrite/general.py", line 154, in controls
_player.pause()
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/models.py", line 42, in wrapper
func(self, *args, **kwargs)
File "/Users/.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
raise err
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/models/player.py", line 82, in pause
await self.user.http.pause_playback(device_id=str(device))
File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/http.py", line 205, in request
raise NotFound(r, data)
spotify.errors.NotFound: Not Found (status code: 404): Device not found```
When I run this code:
token = "user account token"
client_id = 'some client'
secret = 'some secret'
client = spotify.Client(client_id,secret)
async def main():
user = await client.user_from_token(token)
player = await user.get_player()
asyncio.get_event_loop().run_until_complete(main())
I receive the following error:
Warning (from warnings module):
File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164
return await User.from_token(self, token)
RuntimeWarning: coroutine 'WrappedUser.__await__' was never awaited
Warning (from warnings module):
File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164
return await User.from_token(self, token)
RuntimeWarning: coroutine 'User.from_token.<locals>.constructor' was never awaited
Traceback (most recent call last):
File "E:/Projects/assistant/test.py", line 16, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py", line 616, in run_until_complete
return future.result()
File "E:/Projects/assistant/test.py", line 12, in main
user = await client.user_from_token(token)
File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164, in user_from_token
return await User.from_token(self, token)
TypeError: __await__() returned a coroutine
Python: 3.8.5
Spotify: spotify-0.10.2
AttributeError: module 'spotify' has no attribute 'Client'
in searching example file
Hi it's me again
I was hoping you could allow the recently_played() function in the User class to use the full extent of the function in http.py. Right now, User.recently_played() doesn't take any parameters but HTTPClient.recently_played() is as follows
recently_played(*, limit: Optional[int] = 20, before: Optional[str] = None, after: Optional[str] = None) → Awaitable[T_co]
Hi @mental32 -- thanks for the great module.
Stumbled upon this by accident:
1. Search for "Adventure Club" via search method yields empty list:
client.search("Adventure Club", types=["artist"])
{'artists': []}
2. Whereas search for "Adventure" yields "Adventure Club" in top spot:
client.search("Adventure", types=["artist"])
{'artists': [<spotify.Artist: "Adventure Club">,
<spotify.Artist: "Adventure Time">,
<spotify.Artist: "A Space Love Adventure">,
<spotify.Artist: "Leaf Adventure">,
<spotify.Artist: "Sonic Adventure Music Experience">,
<spotify.Artist: "Cast - Olaf's Frozen Adventure">,
<spotify.Artist: "Adventure Galley">,
<spotify.Artist: "Action/Adventure">,
<spotify.Artist: "Adventure Tiger">,
<spotify.Artist: "A Great Adventure or Nothing">,
<spotify.Artist: "Adventure Violence">,
<spotify.Artist: "Sonic Adventure Project">,
<spotify.Artist: "Techno Kitten Adventure">,
<spotify.Artist: "Grand Space Adventure">,
<spotify.Artist: "Adventure">,
<spotify.Artist: "Inner Adventure">,
<spotify.Artist: "Adventure Time">,
<spotify.Artist: "New Street Adventure">,
<spotify.Artist: "Mr. Bacon's Big Adventure">,
<spotify.Artist: "Hand-Me-Down Adventure">]}
3. And: Using Spotify's API sandbox to issue the query in 1 also yields the correct results:
https://developer.spotify.com/console/get-search-item/?q=Adventure%20Club&type=artist&market=&limit=&offset=
yields:
{
"artists": {
"href": "https://api.spotify.com/v1/search?query=Adventure+Club&type=artist&market=CH&offset=0&limit=20",
"items": [
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/5CdJjUi9f0cVgo9nFuJrFa"
},
"followers": {
"href": null,
"total": 228604
},
"genres": [
"big room",
"brostep",
"canadian electronic",
"chillstep",
"complextro",
"edm",
"electro house",
"electronic trap",
"filthstep",
"pop edm"
],
"href": "https://api.spotify.com/v1/artists/5CdJjUi9f0cVgo9nFuJrFa",
"id": "5CdJjUi9f0cVgo9nFuJrFa",
"images": [
{
"height": 640,
"url": "https://i.scdn.co/image/3b0069fe11e50bba1b737a3189f10a78ef3bf8ce",
"width": 640
},
{
"height": 320,
"url": "https://i.scdn.co/image/866b5d49f54160f76d3da80d790b5c5f420996af",
"width": 320
},
{
"height": 160,
"url": "https://i.scdn.co/image/c3299135895611be2fdb223d844b6e720cd8dd44",
"width": 160
}
],
"name": "Adventure Club",
"popularity": 61,
"type": "artist",
"uri": "spotify:artist:5CdJjUi9f0cVgo9nFuJrFa"
},
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/4vIDWbhxIjNMXQ18tfmLxS"
},
"followers": {
"href": null,
"total": 1071
},
"genres": [],
"href": "https://api.spotify.com/v1/artists/4vIDWbhxIjNMXQ18tfmLxS",
"id": "4vIDWbhxIjNMXQ18tfmLxS",
"images": [
{
"height": 640,
"url":
[...]
Do you have an idea what is going on here?
Thanks!
Player.progress_ms
is only set when the player is initialised. After the initialization the value will stay the same.
Expected behaviour: The progress_ms
should always be the current value
I would propose one of the following:
progress_ms
at all. It is not really needed as the progress can easily be obtained with User.currently_playing()['progress_ms']
@property
to fetch the current progressprogress_ms
value with a coroutine for the websocket.I think option 3 would be the best way. However it does not look like it will come soon.
Hi!
I'm testing the flask example and I found this issue with the sync version of the library:
Python 3.7.4 (default, Jul 16 2019, 07:12:58)
[GCC 9.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import spotify.sync as spotify
>>> spotify.Client('foo','bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/sync/models.py", line 81, in __init__
super().__init__(*args, **kwargs)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/client.py", line 61, in __init__
self.http = self._default_http_client(client_id, client_secret, loop=loop)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/sync/models.py", line 20, in __init__
super().__init__(*args, **kwargs)
TypeError: super(type, obj): obj must be an instance or subtype of type
>>>
explicitly specifying the base class seems to be fixing it (like this), but I'm not sure that's required.
UnboundLocalError: local variable 'payload' referenced before assignment
is thrown when attempting to get a track from ID.
Further research into the source code points the error at line 1597 in track, where if market is not defined the payload variable isn't created.
I noticed there is an error in the token refreshing code.
You are using the "Authorization": "Bearer " + access_token header but spotify wants you to use the "Authorization": f"Basic {token.decode()}" like is used your HttpClient.get_bearer_info function.
I worked around the problem by overloading User._refreshing_token with this code:
async def _refreshing_token(self, expires: int, token: str):
while True:
import asyncio
await asyncio.sleep(expires-1)
REFRESH_TOKEN_URL = "https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token={refresh_token}"
route = ("POST", REFRESH_TOKEN_URL.format(refresh_token=token))
from base64 import b64encode
auth = b64encode(":".join((os.environ['SPOTIFY_CLIENT_ID'], os.environ['SPOTIFY_CLIENT_SECRET'])).encode())
try:
data = await self.client.http.request(
route,
headers={"Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {auth.decode()}"}
)
expires = data["expires_in"]
self.http.token = data["access_token"]
print('token refreshed', data["access_token"])
except:
import traceback
traceback.print_exc()
Also in I believe you want to change:
"Content-Type": kwargs.get("content_type", "application/json"),
to
"Content-Type": kwargs.pop("content_type", "application/json"),
in HTTPClient.request otherwise it errors when the content_type kwd is passed on.
I could do a pull request if you'd like. I really like your library, its very pythonic and much easier to use than the other spotify libraries I tried.
I am trying to change the description of a playlist on master.
await user.edit_playlist(playlist, description=description)
File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/src/spotify/spotify/models/user.py", line 416, in edit_playlist
await self.http.change_playlist_details(self.id, to_id(str(playlist)), **data) # type: ignore
TypeError: change_playlist_details() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
As per the title, When creating a spotify.Playlist
from the result of getting a playlist using the HttpClient an error is produced.
Traceback (most recent call last):
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
ret = await coro(*args, **kwargs)
File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\music.py", line 86, in play
playlist = spotify.Playlist(self.bot.spotify, await self.bot.http_spotify.get_playlist(url_id))
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 143, in __init__
self.__from_raw(data)
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 183, in __from_raw
if "items" in data["tracks"]
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 182, in <genexpr>
tuple(PlaylistTrack(client, item) for item in data["tracks"]["items"])
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 109, in __init__
super().__init__(client, data["track"])
File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 53, in __init__
Artist(client, artist) for artist in data.pop("artists", [None])
AttributeError: 'NoneType' object has no attribute 'pop'
I'm new to this so forgive me in advance
I'm following the Flask example, and whenever I try to access the tracks
attribute of Playlist, it gives me None
. I'm also not able to use the remove()
method of Playlist, and it throws me ValueError: list.remove(x): x not in list
since tracks
is None
def start():
currentUser: spotify.User = SPOTIFY_USERS[flask.session['spotify_user_id']]
if currentUser.id in listeningSessions:
return flask.redirect(flask.url_for('.queue'))
try:
currentUser.currently_playing()['item']
except KeyError:
flask.flash("Playback device not found.. Please open your Spotify app and begin playing (any random song)",
category='error')
return flask.redirect(flask.url_for('.index')) # todo handle error
playlists = currentUser.get_playlists()
playlist: spotify.Playlist = None
for p in playlists:
print(p.name, p.name == 'Spo2fi Queue') # Spo2fi Queue True
if p.name == 'Spo2fi Queue':
playlist = p
break
print(playlist.tracks) # None
if not playlist:
playlist = currentUser.create_playlist('Spo2fi Queue', collaborative=True, public=False)
print(playlist.tracks) # None
try:
if currentUser.currently_playing()['is_playing']:
playlist.replace_tracks(currentUser.currently_playing()['item'])
except KeyError:
usersTopTracks = currentUser.top_tracks(limit=1, time_range='medium_term')
print(usersTopTracks)
playlist.replace_tracks(usersTopTracks[0])
print(playlist.tracks) # None
joinId = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in range(4))
while joinId in parties:
joinId = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in range(4))
listeningSessions[currentUser.id] = ListeningSession(currentUser, [], playlist, joinId)
parties[joinId] = currentUser.id
print(playlist.tracks) # None
print(currentUser.currently_playing()) # works
flask.session['party'] = listeningSessions[currentUser.id].joinId
print(flask.session['party']) # works
try:
currentUser.get_player().play(playlist)
currentUser.get_player().shuffle(False)
except spotify.errors.NotFound:
flask.flash("Playback device not found.. Please open your Spotify app and begin playing (any random song)",
category='error')
return flask.redirect(flask.url_for('.index')) # todo handle error
print(playlist.tracks) # None
return flask.redirect(flask.url_for('.queue'))
Hi!
I'm currently testing the Flask example for authenticating (I applied these two small changes, but everything else is a fresh clone from master, v 0.6.0). I'm getting an error from the OAuth module when trying to set up the scopes:
Traceback (most recent call last):
File "spotify_test3.py", line 11, in <module>
OAUTH2: spotify.OAuth2 = spotify.OAuth2(SPOTIFY_CLIENT.id, REDIRECT_URI, scopes=['user-modify-playback-state','user-read-currently-playing','user-read-playback-state'])
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 105, in __init__
self.set_scopes(**scopes)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 178, in set_scopes
method = self.scopes.add if bool(state) else self.scopes.remove
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 135, in scopes
return MappingProxyType(self.__scopes)
TypeError: mappingproxy() argument must be a mapping, not set
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f1c7332d190>
In my case the scopes
property is called with self.__scopes as a set (I don't see that attributed changing anywhere), but even if I call it with a dictionary, the MappingProxyType
object will not have an .add
(or .remove
) method anyway. So I'm a bit lost
FYI: many things you're doing in your code are pretty new to me, so apologies for the dumb questions
Regardless of what parameters you set for user.top_artists(), such as:
**user.top_artists(limit=50,offset=0, time_range="long_term")**
You'll still get the default limit, offset and time range regardless of what you set the params to.
I managed to fix it locally by fiddling around with the library. I believe the problem is that the payload in the function top_artists_or_tracks(), is not sent the HTTP request.
I fixed it by changing line 854 in http.py from
return self.request(route)
to
return self.request(route, params=payload)
I'd make a pull request but I'm new at this stuff.
The make install
command still uses the setup.py file which was replaced by the pyproject.toml. I do know that the correct way to install this is with pip3 install -U .
(This is not really important, I just find it weird that the makefile references an obsolete file.)
Hi,
I'm still working on the flask OAuth example. I'm currently able to give my user consent and get to the redirect; flask is throwing an error on Object of type User is not JSON serializable
.
FYI this is already including the two minor fixes I mention in the other two open issues, plus an added APP.secret_key
on the flask program (required to be able to write/read from session)
* Serving Flask app "spotify_test3" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:8888/ (Press CTRL+C to quit)
127.0.0.1 - - [26/Sep/2019 17:09:36] "GET / HTTP/1.1" 302 -
[2019-09-26 17:09:38,659] ERROR in app: Exception on /spotify/callback [GET]
Traceback (most recent call last):
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
return self.finalize_request(rv)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
response = self.process_response(response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
self.session_interface.save_session(self, ctx.session, response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
val = self.get_signing_serializer(app).dumps(dict(session))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
payload = want_bytes(self.dump_payload(obj))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
return dumps(self.tag(value), separators=(",", ":"))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
rv = _json.dumps(obj, **kwargs)
File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
**kw).encode(obj)
File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable
[2019-09-26 17:09:38,669] ERROR in app: Request finalizing failed with an error while handling an error
Traceback (most recent call last):
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
return self.finalize_request(rv)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
response = self.process_response(response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
self.session_interface.save_session(self, ctx.session, response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
val = self.get_signing_serializer(app).dumps(dict(session))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
payload = want_bytes(self.dump_payload(obj))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
return dumps(self.tag(value), separators=(",", ":"))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
rv = _json.dumps(obj, **kwargs)
File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
**kw).encode(obj)
File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
response = self.process_response(response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
self.session_interface.save_session(self, ctx.session, response)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
val = self.get_signing_serializer(app).dumps(dict(session))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
payload = want_bytes(self.dump_payload(obj))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
return dumps(self.tag(value), separators=(",", ":"))
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
rv = _json.dumps(obj, **kwargs)
File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
**kw).encode(obj)
File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable
127.0.0.1 - - [26/Sep/2019 17:09:38] "GET /spotify/callback?code=AQAHJwiOILULHRJRKWkt_u-XP6kIYgmb4wAtHjW5pVkIhrW_rfYnse-YOosYb9TAjSV5Gusl4su2-T3Iqv3zXd_PILQNOA73WzJTlzlHmRsSnQbYKZLzHTCoXAMlyo7kKDtf6CzB4M-rrdwu3TYCT4Gdb6i0RlYcsPhnOv2H0aeaLv_GwqVL8VtwnKZ9DvpOWclE3N_pYBlMPuRx_z1Hu2VuQDRe7QQdtHMSYJMMvUC-dOEXTLOnSswWji94YfHFVb4iXLQhCizWQ1czS830I_b883MwoIseUwzAzBl4_kWMubT33Ue5CU5VvLtqhzOouQ HTTP/1.1" 500 -
127.0.0.1 - - [26/Sep/2019 17:09:41] "GET /favicon.ico HTTP/1.1" 404 -
Hi!
So, I'm trying to use this library, byt apparentely I cannot use it in python3.5 as i cannot import it. The error is:
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> import spotify
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.5/dist-packages/spotify/__init__.py", line 7, in <module>
from .models import *
File "/usr/local/lib/python3.5/dist-packages/spotify/models/__init__.py", line 7, in <module>
from .playlist import Playlist
File "/usr/local/lib/python3.5/dist-packages/spotify/models/playlist.py", line 29
yield PlaylistTrack(self.__client, track)
^
SyntaxError: 'yield' inside async function
Any way to bypass this?
It seems it doesn't work with VPN or Third Party Proxy Applications
Can You Add Proxy Support to it?
Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x000001E6F763E400> Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001E6F7769F48>, 806136.265)]', '[(<aiohttp.client_proto.ResponseHandler object at 0x000001E6F77A5228>, 806138.421)]'] connector: <aiohttp.connector.TCPConnector object at 0x000001E6F763E438>
On Searching Example with using VPN .
I have created an API using Quart (async Flask) and spotify.py that has async functions as routes that make calls to spotify.search
, spotify.get_artist
, and other functions. When I run the API just using Quart, like with app.run()
, everything works great. However, I am trying to deploy to Heroku, and in order to do that I need to use an ASGI web server called Hypercorn (similar to Gunicorn). When I run the API with hypercorn using hypercorn src.wsgi:app
(where src/wsgi.py
just contains an instance app
of my API from a factory function), I get the following error on all routes:
ERROR in app: Exception on request GET /api/search/Drake
Traceback (most recent call last):
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1451, in handle_request
return await self.full_dispatch_request(request_context)
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1473, in full_dispatch_request
result = await self.handle_user_exception(error)
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 892, in handle_user_exception
raise error
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1471, in full_dispatch_request
result = await self.dispatch_request(request_context)
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1519, in dispatch_request
return await handler(**request_.view_args)
File "/Users/shamaiengar/six-degrees-of-spotify/src/api.py", line 44, in search_artists
results = await clients.spotify.search(artist_name, types=['artist'], limit="20")
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/client.py", line 256, in search
data = await self.http.search(**kwargs)
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/http.py", line 123, in request
self.bearer_info = bearer_info = await self.get_bearer_info()
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/http.py", line 103, in get_bearer_info
async with self._session.post(**kwargs) as resp:
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/client.py", line 1005, in __aenter__
self._resp = await self._coro
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/client.py", line 417, in _request
with timer:
File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/helpers.py", line 568, in __enter__
raise RuntimeError('Timeout context manager should be used '
RuntimeError: Timeout context manager should be used inside a task
I am using spotify.py 0.4.8 and Python 3.7.4. Could I get some advice on how to fix this?
If you want to reproduce this error, you should be able to fork my repo here, run the hypercorn
command above, and try to access a route.
Do you see an interest having examples with an async http server for authentication part ?
I struggle to have a working example with sanic but work is in progress.
When attempting to run the Flask example on the main README I get the following error
/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py:199: RuntimeWarning: coroutine 'WrappedUser.__await__' was never awaited
return await cls.from_token(client, token, refresh_token)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py:199: RuntimeWarning: coroutine 'User.from_token.<locals>.constructor' was never awaited
return await cls.from_token(client, token, refresh_token)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
[2020-09-11 19:39:28,645] ERROR in app: Exception on /spotify/callback [GET]
Traceback (most recent call last):
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "test.py", line 30, in spotify_callback
SPOTIFY_USERS[key] = spotify.User.from_code(
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/sync/models.py", line 45, in wrapped
return self.__client_thread__.run_coroutine_threadsafe(
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/sync/thread.py", line 48, in run_coroutine_threadsafe
return future.result()
File "/Users/user/miniconda3/envs/env22/lib/python3.8/concurrent/futures/_base.py", line 439, in result
return self.__get_result()
File "/Users/user/miniconda3/envs/env22/lib/python3.8/concurrent/futures/_base.py", line 388, in __get_result
raise self._exception
File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py", line 199, in from_code
return await cls.from_token(client, token, refresh_token)
TypeError: __await__() returned a coroutine
I am running the following python environment:
Python 3.8.5
spotify 0.10.2
flask 1.1.2
import spotify
import asyncio
def main():
client = spotify.Client('SomeID', 'SomeSecret')
results = await client.search('drake', limit=5, offset=20, market='JP')
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
I get this error:
SyntaxError: invalid syntax PS C:\Users\Cipher\Desktop\pythonfiles> python .\spotify.py File ".\spotify.py", line 8 results = await client.search('drake') ^ SyntaxError: invalid syntax PS C:\Users\Cipher\Desktop\pythonfiles> python .\spotify.py Traceback (most recent call last): File ".\spotify.py", line 11, in <module> asyncio.get_event_loop().run_until_complete(main()) File ".\spotify.py", line 7, in main client = spotify.Client('SomeID', 'SomeSecret') AttributeError: module 'spotify' has no attribute 'Client'
Please note this is my first time posting an issue but most of the code were copied from the examples to get a grasp of the API.
Thank you,
Cipher.
cannot import name 'OAuth2' from 'spotify.utils'
This adresses a few issues with the current method to play tracks/contexts. It works when given a context but fails when given tracks.
When trying to play a list of tracks with player.play(track01, track02, device=device_id)
the error TypeError: list() takes at most 1 argument (2 given)
is raised. This happens in the HTTPClient method play_playback()
. The list constructor should be used with only one argument, the iterable.
In the same method: The initialisation of can_set_context
tries to access payload["context_uri"]
even if this does not exist.
When trying to play a single track with player.play(track, device=device_id)
the track will be interpreted as a context. It is not possible to play a single track. (This is especially weird when passing the result of a track search into the play function: It fails only for searches with only one search result and works for other results.)
An optional argument treat_as_tracks
would be nice: If set to true it will interpret all *uris as track uris. This way it is possible to pass only one track in cases where you know that it will be a track. This is also backwards compatible as the new argument is optional, the default is the old behaviour.
I have fixed all of the above in my fork and will create a PR, however I am not sure if my fixes are the best way to do this.
Hi. I am Priyansh. I just now installed the library and wanted to extract genre for a given song/track name as input (search query). I cannot see anywhere the functionality to get genre tags for a track.
We can do the same for album but not for track. Is your implementation complete or spotify doesn't provide the genre info. of track ? Meanwhile I find a bug in the api docs. The genres
attribute should changed to genre
for the album class else it will give the error
Does your module support rate limiting? If so, how to activate it? What about caching? Are there any 3rd party modules that you can recommend integrating for that purpose?
I would like to propose some improvements to the API for initializing Spotify Users.
Currently, as of my pull request from a few days ago there are multiple ways to create an authenticated User object:
User.from_code
, User.from_token
, and User.from_refresh_token
.
There is two improvements I'd like to make. First combine the from_token
and from_refresh_token
methods into one, e.g. from_token_info
that takes a tuple of optional parameters, access_token, expiry_time, refresh_token and then correctly instantiates and authenticates the User depending on what tokens it got.
The second improvement is the option to add a cache path to store the access and refresh tokens and later on reinitialize the User by just providing the cache path. This would make it easier to have persistent applications where one does not want to go through the oauth flow every time and does not want to bother with storing the tokens themselves.
I have the changes ready and will make a pull request soon, just wanted to open this issue to allow for discussion about the changes.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.