Browse Source

* added decorator package to setup dependency

* used 'decorator' decorator on the decorators in the decorators module to avoid function signature mangling
* added 'callback' argument and related documentation in the asynchronous methods in api module
Abhinav Sarkar 10 years ago
parent
commit
bbdbfcebb6
3 changed files with 190 additions and 144 deletions
  1. 111
    39
      lastfm/api.py
  2. 67
    93
      lastfm/decorators.py
  3. 12
    12
      setup.py

+ 111
- 39
lastfm/api.py View File

@@ -173,7 +173,8 @@ class Api(object):
173 173
     def get_album(self,
174 174
                  album = None,
175 175
                  artist = None,
176
-                 mbid = None):
176
+                 mbid = None,
177
+                 callback = None):
177 178
         """
178 179
         Get an album object.
179 180
         
@@ -183,6 +184,8 @@ class Api(object):
183 184
         @type artist:    L{str} OR L{Artist}
184 185
         @param mbid:     MBID of the album
185 186
         @type mbid:      L{str}
187
+        @param callback: callback function for asynchronous invocation (optional)
188
+        @type callback:  C{function}
186 189
         
187 190
         @return:         an Album object corresponding the provided album name
188 191
         @rtype:          L{Album}
@@ -192,13 +195,14 @@ class Api(object):
192 195
                                               Otherwise exception is raised.
193 196
                                               
194 197
         @see:            L{Album.get_info}
198
+        @see:            L{async_callback}
195 199
         """
196 200
         if isinstance(artist, Artist):
197 201
             artist = artist.name
198 202
         return Album.get_info(self, artist, album, mbid)
199 203
 
200 204
     @async_callback
201
-    def search_album(self, album, limit = None):
205
+    def search_album(self, album, limit = None, callback = None):
202 206
         """
203 207
         Search for an album by name.
204 208
         
@@ -206,18 +210,19 @@ class Api(object):
206 210
         @type album:      L{str}
207 211
         @param limit:     maximum number of results returned (optional)
208 212
         @type limit:      L{int}
213
+        @param callback:  callback function for asynchronous invocation (optional)
214
+        @type callback:   C{function}
209 215
         
210 216
         @return:          matches sorted by relevance
211 217
         @rtype:           L{lazylist} of L{Album}
212 218
         
213 219
         @see:             L{Album.search}
220
+        @see:             L{async_callback}
214 221
         """
215 222
         return Album.search(self, search_item = album, limit = limit)
216 223
 
217 224
     @async_callback
218
-    def get_artist(self,
219
-                  artist = None,
220
-                  mbid = None):
225
+    def get_artist(self, artist = None, mbid = None, callback = None):
221 226
         """
222 227
         Get an artist object.
223 228
         
@@ -225,6 +230,8 @@ class Api(object):
225 230
         @type artist:     L{str}
226 231
         @param mbid:      MBID of the artist
227 232
         @type mbid:       L{str}
233
+        @param callback:  callback function for asynchronous invocation (optional)
234
+        @type callback:   C{function}
228 235
         
229 236
         @return:         an Artist object corresponding the provided artist name
230 237
         @rtype:          L{Artist}
@@ -233,13 +240,12 @@ class Api(object):
233 240
                                               to be provided. Otherwise exception is raised.
234 241
                                               
235 242
         @see:            L{Artist.get_info}
243
+        @see:            L{async_callback}
236 244
         """
237 245
         return Artist.get_info(self, artist, mbid)
238 246
     
239 247
     @async_callback
240
-    def search_artist(self,
241
-                     artist,
242
-                     limit = None):
248
+    def search_artist(self, artist, limit = None, callback = None):
243 249
         """
244 250
         Search for an artist by name.
245 251
         
@@ -247,21 +253,26 @@ class Api(object):
247 253
         @type artist:     L{str}
248 254
         @param limit:     maximum number of results returned (optional)
249 255
         @type limit:      L{int}
256
+        @param callback:  callback function for asynchronous invocation (optional)
257
+        @type callback:   C{function}
250 258
         
251 259
         @return:          matches sorted by relevance
252 260
         @rtype:           L{lazylist} of L{Artist}
253 261
         
254 262
         @see:             L{Artist.search}
263
+        @see:             L{async_callback}
255 264
         """
256 265
         return Artist.search(self, search_item = artist, limit = limit)
257 266
 
258 267
     @async_callback
259
-    def get_event(self, event):
268
+    def get_event(self, event, callback = None):
260 269
         """
261 270
         Get an event object.
262 271
         
263 272
         @param event:     the event id
264 273
         @type event:      L{int}
274
+        @param callback:  callback function for asynchronous invocation (optional)
275
+        @type callback:   C{function}
265 276
         
266 277
         @return:          an event object corresponding to the event id provided
267 278
         @rtype:           L{Event}
@@ -269,90 +280,113 @@ class Api(object):
269 280
         @raise InvalidParametersError: Exception is raised if an invalid event id is supplied.
270 281
         
271 282
         @see:             L{Event.get_info}
283
+        @see:             L{async_callback}
272 284
         """
273 285
         return Event.get_info(self, event)
274 286
     
275 287
     @async_callback
276
-    def get_location(self, city):
288
+    def get_location(self, city, callback = None):
277 289
         """
278 290
         Get a location object.
279 291
         
280
-        @param city:    the city name
281
-        @type city:     L{str}
292
+        @param city:        the city name
293
+        @type city:         L{str}
294
+        @param callback:    callback function for asynchronous invocation (optional)
295
+        @type callback:     C{function}
282 296
         
283 297
         @return:        a location object corresponding to the city name provided
284 298
         @rtype:         L{Location}
299
+        
300
+        @see:           L{async_callback}
285 301
         """
286 302
         return Location(self, city = city)
287 303
 
288 304
     @async_callback
289
-    def get_country(self, name):
305
+    def get_country(self, name, callback = None):
290 306
         """
291 307
         Get a country object.
292 308
         
293
-        @param name:    the country name
294
-        @type name:     L{str}
309
+        @param name:        the country name
310
+        @type name:         L{str}
311
+        @param callback:    callback function for asynchronous invocation (optional)
312
+        @type callback:     C{function}
295 313
         
296 314
         @return:        a country object corresponding to the country name provided
297 315
         @rtype:         L{Country}
316
+        
317
+        @see:           L{async_callback}
298 318
         """
299 319
         return Country(self, name = name)
300 320
     
301 321
     @async_callback
302
-    def get_group(self, name):
322
+    def get_group(self, name, callback = None):
303 323
         """
304 324
         Get a group object.
305 325
         
306
-        @param name:    the group name
307
-        @type name:     L{str}
326
+        @param name:        the group name
327
+        @type name:         L{str}
328
+        @param callback:    callback function for asynchronous invocation (optional)
329
+        @type callback:     C{function}
308 330
         
309 331
         @return:        a group object corresponding to the group name provided
310 332
         @rtype:         L{Group}
333
+        
334
+        @see:           L{async_callback}
311 335
         """
312 336
         return Group(self, name = name)
313 337
 
314 338
     @async_callback
315
-    def get_playlist(self, url):
339
+    def get_playlist(self, url, callback = None):
316 340
         """
317 341
         Get a playlist object.
318 342
         
319
-        @param url:    lastfm url of the playlist
320
-        @type url:     L{str}
343
+        @param url:        lastfm url of the playlist
344
+        @type url:         L{str}
345
+        @param callback:   callback function for asynchronous invocation (optional)
346
+        @type callback:    C{function}
321 347
         
322 348
         @return:        a playlist object corresponding to the playlist url provided
323 349
         @rtype:         L{Playlist}
324 350
         
325 351
         @see:           L{Playlist.fetch}
352
+        @see:           L{async_callback}
326 353
         """
327 354
         return Playlist.fetch(self, url)
328 355
     
329 356
     @async_callback
330
-    def get_tag(self, name):
357
+    def get_tag(self, name, callback = None):
331 358
         """
332 359
         Get a tag object.
333 360
         
334
-        @param name:    the tag name
335
-        @type name:     L{str}
361
+        @param name:        the tag name
362
+        @type name:         L{str}
363
+        @param callback:    callback function for asynchronous invocation (optional)
364
+        @type callback:     C{function}
336 365
         
337 366
         @return:        a tag object corresponding to the tag name provided
338 367
         @rtype:         L{Tag}
368
+        
369
+        @see:           L{async_callback}
339 370
         """
340 371
         return Tag(self, name = name)
341 372
 
342 373
     @async_callback
343
-    def get_global_top_tags(self):
374
+    def get_global_top_tags(self, callback = None):
344 375
         """
345 376
         Get the top global tags on Last.fm, sorted by popularity (number of times used).
346 377
         
378
+        @param callback: callback function for asynchronous invocation (optional)
379
+        @type callback:  C{function}
380
+        
347 381
         @return:        a list of top global tags
348 382
         @rtype:         L{list} of L{Tag}
383
+        
384
+        @see:           L{async_callback}
349 385
         """
350 386
         return Tag.get_top_tags(self)
351 387
 
352 388
     @async_callback
353
-    def search_tag(self,
354
-                  tag,
355
-                  limit = None):
389
+    def search_tag(self, tag, limit = None, callback = None):
356 390
         """
357 391
         Search for a tag by name.
358 392
         
@@ -360,11 +394,14 @@ class Api(object):
360 394
         @type tag:        L{str}
361 395
         @param limit:     maximum number of results returned (optional)
362 396
         @type limit:      L{int}
397
+        @param callback:  callback function for asynchronous invocation (optional)
398
+        @type callback:   C{function}
363 399
         
364 400
         @return:          matches sorted by relevance
365 401
         @rtype:           L{lazylist} of L{Tag}
366 402
         
367 403
         @see:             L{Tag.search}
404
+        @see:             L{async_callback}
368 405
         """
369 406
         return Tag.search(self, search_item = tag, limit = limit)
370 407
 
@@ -372,7 +409,8 @@ class Api(object):
372 409
     def compare_taste(self,
373 410
                      type1, type2,
374 411
                      value1, value2,
375
-                     limit = None):
412
+                     limit = None,
413
+                     callback = None):
376 414
         """
377 415
         Get a Tasteometer score from two inputs, along with a list of
378 416
         shared artists. If the input is a User or a Myspace URL, some 
@@ -388,16 +426,23 @@ class Api(object):
388 426
         @type value2:    L{str}
389 427
         @param limit:    maximum number of results returned (optional)
390 428
         @type limit:     L{int}
429
+        @param callback: callback function for asynchronous invocation (optional)
430
+        @type callback:  C{function}
391 431
         
392 432
         @return:         the taste-o-meter score for the inputs
393 433
         @rtype:          L{Tasteometer}
394 434
         
395 435
         @see:            L{Tasteometer.compare}
436
+        @see:            L{async_callback}
396 437
         """
397 438
         return Tasteometer.compare(self, type1, type2, value1, value2, limit)
398 439
 
399 440
     @async_callback
400
-    def get_track(self, track, artist = None, mbid = None):
441
+    def get_track(self,
442
+                  track,
443
+                  artist = None,
444
+                  mbid = None,
445
+                  callback = None):
401 446
         """
402 447
         Get a track object.
403 448
         
@@ -407,6 +452,8 @@ class Api(object):
407 452
         @type artist:    L{str} OR L{Artist}
408 453
         @param mbid:     MBID of the track
409 454
         @type mbid:      L{str}
455
+        @param callback: callback function for asynchronous invocation (optional)
456
+        @type callback:  C{function}
410 457
         
411 458
         @return:         a track object corresponding to the track name provided
412 459
         @rtype:          L{Track}
@@ -415,13 +462,18 @@ class Api(object):
415 462
                                               to be provided. Otherwise exception is raised.
416 463
                                               
417 464
         @see:            L{Track.get_info}
465
+        @see:            L{async_callback}
418 466
         """
419 467
         if isinstance(artist, Artist):
420 468
             artist = artist.name
421 469
         return Track.get_info(self, artist, track, mbid)
422 470
     
423 471
     @async_callback
424
-    def search_track(self, track, artist = None, limit = None):
472
+    def search_track(self,
473
+                     track, 
474
+                     artist = None, 
475
+                     limit = None, 
476
+                     callback = None):
425 477
         """
426 478
         Search for a track by name.
427 479
         
@@ -431,23 +483,28 @@ class Api(object):
431 483
         @type artist:     L{str} OR L{Artist}  
432 484
         @param limit:     maximum number of results returned (optional)
433 485
         @type limit:      L{int}
486
+        @param callback:  callback function for asynchronous invocation (optional)
487
+        @type callback:   C{function}
434 488
         
435 489
         @return:          matches sorted by relevance
436 490
         @rtype:           L{lazylist} of L{Track}
437 491
         
438 492
         @see:             L{Track.search}
493
+        @see:             L{async_callback}
439 494
         """
440 495
         if isinstance(artist, Artist):
441 496
             artist = artist.name
442 497
         return Track.search(self, search_item = track, limit = limit, artist = artist)
443 498
 
444 499
     @async_callback
445
-    def get_user(self, name):
500
+    def get_user(self, name, callback = None):
446 501
         """
447 502
         Get an user object.
448 503
         
449
-        @param name:    the last.fm user name
450
-        @type name:     L{str}
504
+        @param name:        the last.fm user name
505
+        @type name:         L{str}
506
+        @param callback:    callback function for asynchronous invocation (optional)
507
+        @type callback:     C{function}
451 508
         
452 509
         @return:        an user object corresponding to the user name provided
453 510
         @rtype:         L{User}
@@ -455,18 +512,23 @@ class Api(object):
455 512
         @raise InvalidParametersError: Exception is raised if an invalid user name is supplied.
456 513
         
457 514
         @see:           L{User.get_info}
515
+        @see:           L{async_callback}
458 516
         """
459 517
         return User.get_info(self, name = name)
460 518
 
461 519
     @async_callback
462
-    def get_authenticated_user(self):
520
+    def get_authenticated_user(self, callback = None):
463 521
         """
464 522
         Get the currently authenticated user.
465 523
         
524
+        @param callback: callback function for asynchronous invocation (optional)
525
+        @type callback: C{function}
526
+        
466 527
         @return:     The currently authenticated user if the session is authenticated
467 528
         @rtype:      L{User}
468 529
         
469 530
         @see:        L{User.get_authenticated_user}
531
+        @see:        L{async_callback}
470 532
         """
471 533
         if self.session_key is not None:
472 534
             return User.get_authenticated_user(self)
@@ -474,12 +536,14 @@ class Api(object):
474 536
             raise AuthenticationFailedError("session key must be present to call this method")
475 537
     
476 538
     @async_callback
477
-    def get_venue(self, venue):
539
+    def get_venue(self, venue, callback = None):
478 540
         """
479 541
         Get a venue object.
480 542
         
481
-        @param venue:    the venue name
482
-        @type venue:     L{str}
543
+        @param venue:        the venue name
544
+        @type venue:         L{str}
545
+        @param callback:     callback function for asynchronous invocation (optional)
546
+        @type callback:      C{function}
483 547
         
484 548
         @return:         a venue object corresponding to the venue name provided
485 549
         @rtype:          L{Venue}
@@ -487,6 +551,7 @@ class Api(object):
487 551
         @raise InvalidParametersError: Exception is raised if an non-existant venue name is supplied.
488 552
         
489 553
         @see:            L{search_venue}
554
+        @see:            L{async_callback}
490 555
         """
491 556
         try:
492 557
             return self.search_venue(venue)[0]
@@ -494,7 +559,11 @@ class Api(object):
494 559
             raise InvalidParametersError("No such venue exists")
495 560
     
496 561
     @async_callback
497
-    def search_venue(self, venue, limit = None, country = None):
562
+    def search_venue(self, 
563
+                     venue, 
564
+                     limit = None, 
565
+                     country = None,
566
+                     callback = None):
498 567
         """
499 568
         Search for a venue by name.
500 569
         
@@ -505,11 +574,14 @@ class Api(object):
505 574
         @type country:    L{str}  
506 575
         @param limit:     maximum number of results returned (optional)
507 576
         @type limit:      L{int}
577
+        @param callback:  callback function for asynchronous invocation (optional)
578
+        @type callback:   C{function}
508 579
         
509 580
         @return:          matches sorted by relevance
510 581
         @rtype:           L{lazylist} of L{Venue}
511 582
         
512 583
         @see:             L{Venue.search}
584
+        @see:             L{async_callback}
513 585
         """
514 586
         return Venue.search(self, search_item = venue, limit = limit, country = country)
515 587
 

+ 67
- 93
lastfm/decorators.py View File

@@ -6,6 +6,8 @@ __version__ = "0.2"
6 6
 __license__ = "GNU Lesser General Public License"
7 7
 __package__ = "lastfm"
8 8
 
9
+from decorator import decorator
10
+
9 11
 def top_property(list_property_name):
10 12
     """
11 13
     A decorator to return a property that returns the first value of list 
@@ -57,7 +59,8 @@ def cached_property(func):
57 59
 
58 60
     return property(fget = wrapper, doc = func.__doc__)
59 61
 
60
-def authenticate(func):
62
+@decorator
63
+def authenticate(func, *args, **kwargs):
61 64
     """
62 65
     A decorator to check if the current user is authenticated or not. Used only
63 66
     on the functions that need authentication. If not authenticated then an
@@ -72,34 +75,33 @@ def authenticate(func):
72 75
     @raise AuthenticationFailedError: If the user is not authenticated, then an
73 76
                                       exception is raised.
74 77
     """
75
-    def wrapper(self, *args, **kwargs):
76
-        from lastfm.user import User, Api
77
-        username = None
78
-        if isinstance(self, User):
79
-            username = self.name
80
-            if self.authenticated:
81
-                return func(self, *args, **kwargs)
82
-        elif hasattr(self, 'user'):
83
-            username = self.user.name
84
-            if self.user.authenticated:
85
-                return func(self, *args, **kwargs)
86
-        elif hasattr(self, '_subject') and isinstance(self._subject, User):
87
-            username = self._subject.name
88
-            if self._subject.authenticated:
89
-                return func(self, *args, **kwargs)
90
-        elif hasattr(self, '_api') and isinstance(self._api, Api):
91
-            try:
92
-                user = self._api.get_authenticated_user()
93
-                username = user.name
94
-                return func(self, *args, **kwargs)
95
-            except AuthenticationFailedError:
96
-                pass
97
-        raise AuthenticationFailedError(
98
-            "user '%s' does not have permissions to access the service" % username)
99
-    wrapper.__doc__ = func.__doc__
100
-    return wrapper
78
+    self = args[0]
79
+    from lastfm.user import User, Api
80
+    username = None
81
+    if isinstance(self, User):
82
+        username = self.name
83
+        if self.authenticated:
84
+            return func(self, *args, **kwargs)
85
+    elif hasattr(self, 'user'):
86
+        username = self.user.name
87
+        if self.user.authenticated:
88
+            return func(self, *args, **kwargs)
89
+    elif hasattr(self, '_subject') and isinstance(self._subject, User):
90
+        username = self._subject.name
91
+        if self._subject.authenticated:
92
+            return func(self, *args, **kwargs)
93
+    elif hasattr(self, '_api') and isinstance(self._api, Api):
94
+        try:
95
+            user = self._api.get_authenticated_user()
96
+            username = user.name
97
+            return func(self, *args, **kwargs)
98
+        except AuthenticationFailedError:
99
+            pass
100
+    raise AuthenticationFailedError(
101
+        "user '%s' does not have permissions to access the service" % username)
101 102
 
102
-def depaginate(func):
103
+@decorator
104
+def depaginate(func, *args, **kwargs):
103 105
     """
104 106
     A decorator to depaginate the search results.
105 107
     
@@ -111,24 +113,22 @@ def depaginate(func):
111 113
     @rtype:         C{function}
112 114
     """
113 115
     from lastfm.lazylist import lazylist
114
-    def wrapper(*args, **kwargs):
115
-        @lazylist
116
-        def generator(lst):
116
+    @lazylist
117
+    def generator(lst):
118
+        gen = func(*args, **kwargs)
119
+        total_pages = gen.next()
120
+        for e in gen:
121
+            yield e
122
+        for page in xrange(2, total_pages+1):
123
+            kwargs['page'] = page
117 124
             gen = func(*args, **kwargs)
118
-            total_pages = gen.next()
125
+            gen.next()
119 126
             for e in gen:
120 127
                 yield e
121
-            for page in xrange(2, total_pages+1):
122
-                kwargs['page'] = page
123
-                gen = func(*args, **kwargs)
124
-                gen.next()
125
-                for e in gen:
126
-                    yield e
127
-        return generator()
128
-    wrapper.__doc__ = func.__doc__
129
-    return wrapper
130
-
131
-def async_callback(func):
128
+    return generator()
129
+    
130
+@decorator
131
+def async_callback(func, *args, **kwargs):
132 132
     """
133 133
     A decorator to convert a synchronous (blocking) function into 
134 134
     an asynchronous (non-blocking) function.
@@ -154,56 +154,30 @@ def async_callback(func):
154 154
     @rtype:         C{function}
155 155
     """
156 156
     from threading import Thread
157
-    def wrapper(self, *args, **kwargs):
158
-        callback = None
159
-        for a in args:
160
-            if callable(a):
161
-                callback = a
162
-                args = list(args)
163
-                args.remove(a)
164
-                args = tuple(args)
165
-                break
166
-        if 'callback' in kwargs:
167
-            callback = kwargs['callback']
168
-            del kwargs['callback']
169
-            
170
-        if (callback is not None and callable(callback)):
171
-            def async_call():
172
-                result = None
173
-                try:
174
-                    result = func(self, *args, **kwargs)
175
-                except Exception, e:
176
-                    result = e
177
-                callback(result)
178
-            thread = Thread(target = async_call)
179
-            thread.start()
180
-            return
181
-        return func(self, *args, **kwargs)
182
-            
183
-    wrapper.__doc__ = "%s\n        @see: L{async_callback}" % func.__doc__
184
-    return wrapper
185
-
186
-def _get_arg_string(argspecs):
187
-    arg_str = ""    
188
-    args = argspecs.args
189
-    args.remove('self')
190
-    defaults = argspecs.defaults
191
-    
192
-    if defaults is not None:
193
-        defargs = args[-len(defaults):]
194
-        nondefargs = args[:-len(defaults)]
195
-        nondefargs_str = ", ".join(nondefargs)
196
-        defargs_str = ", ".join(["%s = %s" % (defargs[i], defaults[i]) for i in xrange(len(defargs))])
197
-        if nondefargs_str != '' and defargs_str != '':
198
-            arg_str = "%s, %s" % (nondefargs_str, defargs_str)
199
-        elif nondefargs_str != '':
200
-            arg_str = nondefargs_str
201
-        elif defargs_str != '':
202
-            arg_str = defargs_str
203
-    else:
204
-        arg_str = ", ".join(args)
205
-    print arg_str
206
-    return arg_str
157
+    callback = None
158
+    for a in args:
159
+        if callable(a):
160
+            callback = a
161
+            args = list(args)
162
+            args.remove(a)
163
+            args = tuple(args)
164
+            break
165
+    if 'callback' in kwargs:
166
+        callback = kwargs['callback']
167
+        del kwargs['callback']
168
+    
169
+    if callback is not None and callable(callback):
170
+        def async_call():
171
+            result = None
172
+            try:
173
+                result = func(*args, **kwargs)
174
+            except Exception, e:
175
+                result = e
176
+            callback(result)
177
+        thread = Thread(target = async_call)
178
+        thread.start()
179
+        return
180
+    return func(*args, **kwargs)
207 181
 
208 182
 import copy
209 183
 from lastfm.error import LastfmError, AuthenticationFailedError

+ 12
- 12
setup.py View File

@@ -22,7 +22,7 @@ located at http://ws.audioscrobbler.com/2.0/ .""",
22 22
 )
23 23
 
24 24
 SETUPTOOLS_METADATA = dict(
25
-	install_requires = ['setuptools'],
25
+	install_requires = ['setuptools', 'decorator'],
26 26
 	include_package_data = True,
27 27
     tests_require = ['wsgi_intercept'],
28 28
 	classifiers = [
@@ -40,18 +40,18 @@ SETUPTOOLS_METADATA = dict(
40 40
 
41 41
 import sys
42 42
 if sys.version < '2.5':
43
-	SETUPTOOLS_METADATA['install_requires'].append('ElementTree')
44
-	SETUPTOOLS_METADATA['install_requires'].append('cElementTree')
43
+    SETUPTOOLS_METADATA['install_requires'].append('ElementTree')
44
+    SETUPTOOLS_METADATA['install_requires'].append('cElementTree')
45 45
 
46 46
 def Main():
47
-  # Use setuptools if available, otherwise fallback and use distutils
48
-  try:
49
-    import setuptools
50
-    METADATA.update(SETUPTOOLS_METADATA)
51
-    setuptools.setup(**METADATA)
52
-  except ImportError:
53
-    import distutils.core
54
-    distutils.core.setup(**METADATA)
47
+    # Use setuptools if available, otherwise fallback and use distutils
48
+    try:
49
+        import setuptools
50
+        METADATA.update(SETUPTOOLS_METADATA)
51
+        setuptools.setup(**METADATA)
52
+    except ImportError:
53
+        import distutils.core
54
+        distutils.core.setup(**METADATA)
55 55
 
56 56
 if __name__ == '__main__':
57
-  Main()
57
+    Main()

Loading…
Cancel
Save