diff --git a/README.mkd b/README.mkd index 01b1049..4a0c725 100644 --- a/README.mkd +++ b/README.mkd @@ -1,8 +1,10 @@ -# Discogs API Client +# python3-discogs-client -This is an active fork of the official Discogs API client for Python, which was deprecated by discogs.com as of June 2020. We think it is a very useful Python module and decided to continue maintaining it. If you'd like to contribute your code or ideas into the module, you are very welcome to file a pull-request as described [here](#contributing) or open an [issue](https://github.com/joalla/discogs_client/issues) and tell us what you think. +This is an active fork of the official "Discogs API client for Python", which was deprecated by discogs.com as of June 2020. We think it is a very useful Python module and decided to continue maintaining it. If you'd like to contribute your code, you are very welcome to submit a pull-request as described [here](#contributing). -The Discogs API client enables you to query the Discogs database for information on artists, releases, labels, users, Marketplace listings, and more. It also supports OAuth 1.0a authorization, which allows you to change user data such as profile information, collections and wantlists, inventory, and orders. +python3-discogs-client enables you to query the Discogs database (discogs.com) through its REST-API for information on artists, releases, labels, users, Marketplace listings, and more. It also supports OAuth 1.0a authorization, which allows you to change user data such as profile information, collections and wantlists, inventory, and orders. + +Find usage information on this README page or search and ask in the API section of the Discogs developer forum at https://www.discogs.com/forum/topic/1082. [![Build Status](https://travis-ci.org/discogs/discogs_client.png?branch=master)](https://travis-ci.org/discogs/discogs_client) [![Coverage Status](https://coveralls.io/repos/discogs/discogs_client/badge.png)](https://coveralls.io/r/discogs/discogs_client) diff --git a/discogs_client/fetchers.py b/discogs_client/fetchers.py index a408a4c..ecf3d25 100644 --- a/discogs_client/fetchers.py +++ b/discogs_client/fetchers.py @@ -50,6 +50,10 @@ class RequestsFetcher(Fetcher): """Fetches via HTTP from the Discogs API.""" def fetch(self, client, method, url, data=None, headers=None, json=True): resp = requests.request(method, url, data=data, headers=headers) + self.rate_limit = resp.headers.get( + 'X-Discogs-Ratelimit') + self.rate_limit_used = resp.headers.get( + 'X-Discogs-Ratelimit-Used') self.rate_limit_remaining = resp.headers.get( 'X-Discogs-Ratelimit-Remaining') return resp.content, resp.status_code @@ -63,6 +67,10 @@ def __init__(self, user_token): def fetch(self, client, method, url, data=None, headers=None, json=True): resp = requests.request(method, url, params={'token':self.user_token}, data=data, headers=headers) + self.rate_limit = resp.headers.get( + 'X-Discogs-Ratelimit') + self.rate_limit_used = resp.headers.get( + 'X-Discogs-Ratelimit-Used') self.rate_limit_remaining = resp.headers.get( 'X-Discogs-Ratelimit-Remaining') return resp.content, resp.status_code @@ -97,6 +105,10 @@ def fetch(self, client, method, url, data=None, headers=None, json_format=True): body=data, headers=headers) resp = request(method, uri, headers=headers, data=body) + self.rate_limit = resp.headers.get( + 'X-Discogs-Ratelimit') + self.rate_limit_used = resp.headers.get( + 'X-Discogs-Ratelimit-Used') self.rate_limit_remaining = resp.headers.get( 'X-Discogs-Ratelimit-Remaining') return resp.content, resp.status_code diff --git a/discogs_client/models.py b/discogs_client/models.py index bbb3eb7..310fa3a 100644 --- a/discogs_client/models.py +++ b/discogs_client/models.py @@ -620,6 +620,11 @@ def releases(self): # TODO: Needs releases_url return PaginatedList(self.client, self.fetch('resource_url') + '/releases', 'releases', CollectionItemInstance) + def add_release(self, release): + release_id = release.id if isinstance(release, Release) else release + add_release_url = self.fetch('resource_url') + '/releases/{}'.format(release_id) + self.client._post(add_release_url, None) + def __repr__(self): return self.repr_str(''.format(self.id, self.name)) diff --git a/discogs_client/tests/res/users/example/collection/folders.json b/discogs_client/tests/res/users/example/collection/folders.json new file mode 100644 index 0000000..95473d7 --- /dev/null +++ b/discogs_client/tests/res/users/example/collection/folders.json @@ -0,0 +1,22 @@ +{ + "folders": [ + { + "id": 0, + "name": "All", + "count": 3, + "resource_url": "/users/example/collection/folders/0" + }, + { + "id": 1, + "name": "Uncategorized folder", + "count": 0, + "resource_url": "/users/example/collection/folders/1" + }, + { + "id": 2, + "name": "Collection folder 2", + "count": 0, + "resource_url": "/users/example/collection/folders/2" + } + ] +} diff --git a/discogs_client/tests/res/users/example/collection/folders/0/releases_page=1&per_page=50.json b/discogs_client/tests/res/users/example/collection/folders/0/releases_page=1&per_page=50.json new file mode 100644 index 0000000..9026277 --- /dev/null +++ b/discogs_client/tests/res/users/example/collection/folders/0/releases_page=1&per_page=50.json @@ -0,0 +1,216 @@ +{ + "pagination": { + "items": 3, + "page": 1, + "pages": 1, + "per_page": 50, + "urls": { + "last": "/users/example/collection/folders/0/releases?page=1&per_page=50", + "next": "/users/example/collection/folders/0/releases?page=1&per_page=50" + } + }, + "releases": [ + { + "basic_information": { + "artists": [ + { + "anv": "Kruder Dorfmeister", + "id": 1434, + "join": "", + "name": "Kruder & Dorfmeister", + "resource_url": "/artists/1434", + "role": "", + "tracks": "" + } + ], + "cover_image": "https://img.discogs.com/O_JhZV0Ebg2aXZFubaYYmfZE_ko=/fit-in/600x600/filters:strip_icc():format(jpeg):mode_rgb():quality(90)/discogs-images/R-6794957-1426780517-4728.jpeg.jpg", + "formats": [ + { + "descriptions": [ + "LP", + "Compilation", + "Reissue", + "Remastered" + ], + "name": "Vinyl", + "qty": "5", + "text": "Trifold Cover, 180 g" + } + ], + "genres": [ + "Electronic" + ], + "id": 6794957, + "labels": [ + { + "catno": "!K7073LP", + "entity_type": "1", + "entity_type_name": "Label", + "id": 29377, + "name": "!K7 Records", + "resource_url": "/labels/29377" + } + ], + "master_id": 52323, + "master_url": "/masters/52323", + "resource_url": "/releases/6794957", + "styles": [ + "Downtempo", + "Dub", + "Trip Hop" + ], + "thumb": "https://img.discogs.com/-S3RUg7K9NE9KmXGUJGlfgac7HY=/fit-in/150x150/filters:strip_icc():format(jpeg):mode_rgb():quality(40)/discogs-images/R-6794957-1426780517-4728.jpeg.jpg", + "title": "The K&D Sessions\u2122", + "year": 2015 + }, + "date_added": "2015-04-14T03:23:29-07:00", + "folder_id": 1, + "id": 6794957, + "instance_id": 101, + "rating": 0 + }, + { + "basic_information": { + "artists": [ + { + "anv": "", + "id": 24214, + "join": "/", + "name": "Suburbass", + "resource_url": "/artists/24214", + "role": "", + "tracks": "" + }, + { + "anv": "", + "id": 316643, + "join": "", + "name": "MRMNK", + "resource_url": "/artists/316643", + "role": "", + "tracks": "" + } + ], + "cover_image": "https://img.discogs.com/fJTyWDScMHTWezHjjNO_ytrSQ9A=/fit-in/300x300/filters:strip_icc():format(jpeg):mode_rgb():quality(90)/discogs-images/R-457553-1116175264.jpg.jpg", + "formats": [ + { + "descriptions": [ + "12\"" + ], + "name": "Vinyl", + "qty": "1" + } + ], + "genres": [ + "Electronic" + ], + "id": 457553, + "labels": [ + { + "catno": "10_Traktion 02", + "entity_type": "1", + "entity_type_name": "Label", + "id": 42254, + "name": "10_Traktion", + "resource_url": "/labels/42254" + } + ], + "master_id": 0, + "master_url": null, + "resource_url": "/releases/457553", + "styles": [ + "Techno" + ], + "thumb": "https://img.discogs.com/2taw-jEIUvxbBF0Cj9UWjyACKEg=/fit-in/150x150/filters:strip_icc():format(jpeg):mode_rgb():quality(40)/discogs-images/R-457553-1116175264.jpg.jpg", + "title": "10_Traktion 02", + "year": 2004 + }, + "date_added": "2009-08-20T00:00:00-07:00", + "folder_id": 1, + "id": 457553, + "instance_id": 102, + "rating": 0 + }, + { + "basic_information": { + "artists": [ + { + "anv": "", + "id": 48163, + "join": "Featuring", + "name": "Orion (2)", + "resource_url": "/artists/48163", + "role": "", + "tracks": "" + }, + { + "anv": "", + "id": 485563, + "join": "/", + "name": "Royal (4)", + "resource_url": "/artists/485563", + "role": "", + "tracks": "" + }, + { + "anv": "", + "id": 475678, + "join": "&", + "name": "Theory (3)", + "resource_url": "/artists/475678", + "role": "", + "tracks": "" + }, + { + "anv": "Dr.No", + "id": 475677, + "join": "", + "name": "Dr. Know (4)", + "resource_url": "/artists/475677", + "role": "", + "tracks": "" + } + ], + "cover_image": "https://img.discogs.com/a20zQSOAYQoeA0MWwzKk2QqiSTE=/fit-in/600x597/filters:strip_icc():format(jpeg):mode_rgb():quality(90)/discogs-images/R-656052-1262439560.jpeg.jpg", + "formats": [ + { + "descriptions": [ + "12\"", + "45 RPM" + ], + "name": "Vinyl", + "qty": "1" + } + ], + "genres": [ + "Electronic" + ], + "id": 656052, + "labels": [ + { + "catno": "13M001", + "entity_type": "1", + "entity_type_name": "Label", + "id": 56524, + "name": "13 Music", + "resource_url": "/labels/56524" + } + ], + "master_id": 442393, + "master_url": "/masters/442393", + "resource_url": "/releases/656052", + "styles": [ + "Drum n Bass" + ], + "thumb": "https://img.discogs.com/4bFK4HNtoAsEcZ9b9J8UA0ZlEr4=/fit-in/150x150/filters:strip_icc():format(jpeg):mode_rgb():quality(40)/discogs-images/R-656052-1262439560.jpeg.jpg", + "title": "Control / Command & Conquer", + "year": 2006 + }, + "date_added": "2009-08-20T00:00:00-07:00", + "folder_id": 1, + "id": 656052, + "instance_id": 103, + "rating": 0 + } + ] +} diff --git a/discogs_client/tests/test_models.py b/discogs_client/tests/test_models.py index af31a83..0da7e6e 100644 --- a/discogs_client/tests/test_models.py +++ b/discogs_client/tests/test_models.py @@ -147,6 +147,54 @@ def test_wantlist(self): self.assertEqual(method, 'DELETE') self.assertEqual(url, '/users/example/wants/1') + def test_collection(self): + """Collection folders can be manipulated""" + # Fetch the users collection folders from the filesystem + u = self.d.user('example') + self.assertEqual(len(u.collection_folders), 3) + # Fetch basic information from folders endpoint + self.assertEqual(u.collection_folders[0].id, 0) + self.assertEqual(u.collection_folders[0].name, "All") + self.assertEqual(u.collection_folders[1].id, 1) + self.assertEqual(u.collection_folders[1].name, "Uncategorized folder") + # Fetch details from folders//releases endpoint + self.assertEqual(u.collection_folders[0].releases[2].id, 656052) + self.assertEqual(u.collection_folders[0].releases[2].release.title, "Control / Command & Conquer") + + # Mock expected responses for add_release test - FileSystemFetcher disabled now + self.m._fetcher.fetcher.responses = { + '/users/example/collection/folders': (b''' + {"folders": [{"resource_url": "/users/example/collection/folders/0", + "id": 0, + "name": "All" + }, + {"resource_url": "/users/example/collection/folders/1", + "id": 1, + "name": "Uncategorized folder" + }] + }''', 200), + '/users/example/collection/folders/1': (b'{}', 200), + '/users/example/collection/folders/1/releases/123456': (b'{"instance_id": 123}', 201), + '/users/example/collection/folders/1/releases/1': (b'{"instance_id": 124}', 201), + } + + # Now bind the user to the memory client + u.client = self.m + + # test adding a release by id + u.collection_folders[1].add_release(123456) + method, url, data, headers = self.m._fetcher.last_request + self.assertEqual(method, 'POST') + self.assertEqual(url, '/users/example/collection/folders/1/releases/123456') + + # test adding a release object + r = self.d.release(1) + self.assertEqual(r.title, 'Stockholm') + u.collection_folders[1].add_release(r) + method, url, data, headers = self.m._fetcher.last_request + self.assertEqual(method, 'POST') + self.assertEqual(url, '/users/example/collection/folders/1/releases/1') + def test_delete_object(self): """Can request DELETE on an APIObject""" u = self.d.user('example')