etsy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/README.rdoc +102 -19
  2. data/Rakefile +5 -5
  3. data/lib/etsy.rb +104 -26
  4. data/lib/etsy/basic_client.rb +26 -0
  5. data/lib/etsy/image.rb +19 -16
  6. data/lib/etsy/listing.rb +30 -27
  7. data/lib/etsy/model.rb +12 -49
  8. data/lib/etsy/request.rb +41 -18
  9. data/lib/etsy/response.rb +17 -11
  10. data/lib/etsy/secure_client.rb +79 -0
  11. data/lib/etsy/shop.rb +42 -18
  12. data/lib/etsy/user.rb +39 -34
  13. data/lib/etsy/verification_request.rb +17 -0
  14. data/lib/etsy/version.rb +4 -4
  15. data/test/fixtures/image/findAllListingImages.json +102 -0
  16. data/test/fixtures/listing/findAllShopListingsActive.json +69 -0
  17. data/test/fixtures/shop/findAllShop.json +1 -0
  18. data/test/fixtures/shop/findAllShop.single.json +1 -0
  19. data/test/fixtures/shop/getShop.multiple.json +1 -0
  20. data/test/fixtures/shop/getShop.single.json +32 -0
  21. data/test/fixtures/user/getUser.multiple.json +29 -0
  22. data/test/fixtures/user/getUser.single.json +13 -0
  23. data/test/fixtures/user/getUser.single.private.json +18 -0
  24. data/test/test_helper.rb +27 -42
  25. data/test/unit/etsy/basic_client_test.rb +28 -0
  26. data/test/unit/etsy/image_test.rb +36 -15
  27. data/test/unit/etsy/listing_test.rb +84 -66
  28. data/test/unit/etsy/request_test.rb +121 -39
  29. data/test/unit/etsy/response_test.rb +20 -20
  30. data/test/unit/etsy/secure_client_test.rb +110 -0
  31. data/test/unit/etsy/shop_test.rb +74 -40
  32. data/test/unit/etsy/user_test.rb +65 -61
  33. data/test/unit/etsy/verification_request_test.rb +26 -0
  34. data/test/unit/etsy_test.rb +91 -10
  35. metadata +61 -17
  36. data/test/fixtures/getShopDetails.json +0 -70
  37. data/test/fixtures/getShopListings.json +0 -135
  38. data/test/fixtures/getUserDetails.json +0 -34
data/README.rdoc CHANGED
@@ -8,35 +8,103 @@ The Etsy gem provides a friendly Ruby interface to the Etsy API
8
8
 
9
9
  Installing the latest stable version is simple:
10
10
 
11
- sudo gem install etsy
11
+ $ gem install etsy
12
12
 
13
13
  If you want to be on the bleeding edge, install from GitHub:
14
14
 
15
- sudo gem install reagent-etsy --source=http://gems.github.com
15
+ $ git clone git://github.com/reagent/etsy.git
16
+ $ cd etsy
17
+ $ rake gem && gem install pkg/etsy-<version>.gem
16
18
 
17
19
  == Usage
18
20
 
19
- The Etsy API is read-only - all you need to gain access is an API Key (available
20
- from http://developer.etsy.com). Once you have your API key, set it in your script:
21
+ === Read-Only Mode
22
+
23
+ The Etsy API has two modes: read-only, and read-write. Read-only mode only requires an
24
+ API key (available from http://developer.etsy.com):
21
25
 
22
26
  require 'rubygems'
23
27
  require 'etsy'
28
+
24
29
  Etsy.api_key = 'foobar'
25
30
 
26
- From there, you can make any calls to the API that you need.
31
+ From there, you can make any non-authenticated calls to the API that you need.
32
+
33
+ == Authenticated Calls
34
+
35
+ The Etsy API has support for both retrieval of extended information and write
36
+ support for authenticated users. Authentication can either be performed from
37
+ the console or from within a Ruby web application.
38
+
39
+ === Console
40
+
41
+ For simple authentication from the console, configure the necessary parameters:
42
+
43
+ require 'rubygems'
44
+ require 'etsy'
45
+
46
+ Etsy.api_key = 'key'
47
+ Etsy.api_secret = 'secret'
48
+ Etsy.access_mode = :read_write
49
+
50
+ From there, you will need to paste the verification URL into a browser:
51
+
52
+ Etsy.verification_url
53
+
54
+ Once you have allowed access, you can now generate an access token by supplying
55
+ the verifier displayed on the Etsy site:
56
+
57
+ request = Etsy.request_token
58
+ access = Etsy.access_token(request.token, request.secret, 'abc123')
59
+
60
+ Authenticated calls can now be made by passing an access token and secret:
61
+
62
+ Etsy.myself(access.token, access.secret)
63
+
64
+ === Web Application
65
+
66
+ The process for authenticating via a web application is similar, but requires the
67
+ configuration of a callback URL:
68
+
69
+ require 'rubygems'
70
+ require 'etsy'
71
+
72
+ Etsy.api_key = 'key'
73
+ Etsy.api_secret = 'secret'
74
+ Etsy.access_mode = :read_write
75
+ Etsy.callback_url = 'http://localhost:4567/authorize'
76
+
77
+ In this mode, you'll need to store the request token and secret before redirecting
78
+ to the verification URL. A simple example using Sinatra:
79
+
80
+ enable :sessions
81
+
82
+ get '/' do
83
+ session[:request_token] = Etsy.request_token.token
84
+ session[:request_secret] = Etsy.request_token.secret
85
+ redirect Etsy.verification_url
86
+ end
87
+
88
+ get '/authorize' do
89
+ access_token = Etsy.access_token(
90
+ session[:request_token],
91
+ session[:request_secret],
92
+ params[:oauth_verifier]
93
+ )
94
+
95
+ # access_token.token and access_token.secret can now be saved for future API calls
96
+ end
27
97
 
28
98
  === Users
29
99
 
30
100
  If you're starting with a user, the easiest way is to use the Etsy.user method:
31
101
 
32
102
  >> user = Etsy.user('littletjane')
33
- => #<Etsy::User:0x107f82c @result=[{"city"=>"Washington, DC", ... >
103
+ => #<Etsy::User:0x107f82c @result=[{"city"=>"Washington, DC", ... >
34
104
  >> user.username
35
105
  => "littletjane"
36
106
  >> user.id
37
107
  => 5327518
38
- >> user.url
39
- => "http://www.etsy.com/shop.php?user_id=5327518"
40
108
 
41
109
  For more information about what is available for a user, check out the documentation
42
110
  for Etsy::User.
@@ -46,17 +114,15 @@ for Etsy::User.
46
114
  Each user may optionally have a shop. If a user is a seller, he / she also has an
47
115
  associated shop object:
48
116
 
49
- >> user.seller?
50
- => true
51
117
  >> shop = user.shop
52
118
  => #<Etsy::Shop:0x102578c @result={"is_vacation"=>"", "announcement"=> ... >
53
119
  >> shop.name
54
120
  => "littletjane"
55
121
  >> shop.title
56
122
  => "a cute and crafty mix of handmade goods."
57
-
123
+
58
124
  More information about shops can be found in the documentation for Etsy::Shop.
59
-
125
+
60
126
  == Listings
61
127
 
62
128
  Shops contain multiple listings:
@@ -85,23 +151,40 @@ Each listing has one or more images available:
85
151
  >> listing.images
86
152
  => [#<Etsy::Image:0x18f85e4 @result={} ... >,
87
153
  #<Etsy::Image:0x18f85d0 @result={} ... >]
88
- >> listing.images.first.small_square
89
- => "http://ny-image2.etsy.com/il_25x25.67765346.jpg"
90
- >> listing.images.first.large
91
- => "http://ny-image2.etsy.com/il_430xN.67765346.jpg"
154
+ >> listing.images.first.square
155
+ => "http://ny-image0.etsy.com/il_75x75.189111072.jpg"
156
+ >> listing.images.first.full
157
+ => "http://ny-image0.etsy.com/il_fullxfull.189111072.jpg"
92
158
 
93
159
  Listings also have a primary image:
94
160
 
95
161
  >> listing.image
96
162
  => #<Etsy::Image:0x18c3060 @result={} ... >
97
- >> listing.image.large
98
- => "http://ny-image2.etsy.com/il_430xN.67765346.jpg"
163
+ >> listing.image.full
164
+ => "http://ny-image0.etsy.com/il_fullxfull.189111072.jpg"
99
165
 
100
166
  More information is available in the documentation for Etsy::Image.
101
167
 
168
+ == Contributing
169
+
170
+ I have a "commit bit" policy for contributions to this repository. Once I accept
171
+ your patch, I will give you full commit access. To submit patches:
172
+
173
+ 1. Fork this repository
174
+ 2. Implement the desired feature with tests (and documentation if necessary)
175
+ 3. Send me a pull request
176
+
177
+ I ask that you not submit patches that include changes to the version or gemspec.
178
+
179
+ == Contributors
180
+
181
+ These people have helped make the Etsy gem what it is today:
182
+
183
+ * {Katrina Owen}[http://github.com/kowen]
184
+
102
185
  == License
103
186
 
104
- Copyright (c) 2009 Patrick Reagan (reaganpr@gmail.com)
187
+ Copyright (c) 2009 - 2010 Patrick Reagan (reaganpr@gmail.com)
105
188
 
106
189
  Permission is hereby granted, free of charge, to any person
107
190
  obtaining a copy of this software and associated documentation
data/Rakefile CHANGED
@@ -17,9 +17,9 @@ spec = Gem::Specification.new do |s|
17
17
  s.email = 'reaganpr@gmail.com'
18
18
  s.homepage = 'http://sneaq.net'
19
19
  s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,test}/**/*")
20
- # s.executables = ['etsy']
21
-
22
- s.add_dependency('json', '~> 1.1.0')
20
+
21
+ s.add_dependency('json', '~> 1.4.0')
22
+ s.add_dependency('oauth', '~> 0.4.0')
23
23
  end
24
24
 
25
25
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -32,8 +32,8 @@ Rake::TestTask.new do |t|
32
32
  t.verbose = true
33
33
  end
34
34
 
35
- desc 'Generate the gemspec to serve this Gem from Github'
36
- task :github do
35
+ desc 'Generate the gemspec for this Gem'
36
+ task :gemspec do
37
37
  file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
38
38
  File.open(file, 'w') {|f| f << spec.to_ruby }
39
39
  puts "Created gemspec: #{file}"
data/lib/etsy.rb CHANGED
@@ -2,10 +2,15 @@ $:.unshift File.dirname(__FILE__)
2
2
 
3
3
  require 'net/http'
4
4
  require 'json'
5
+ require 'oauth'
5
6
 
6
7
  require 'etsy/request'
7
8
  require 'etsy/response'
8
9
 
10
+ require 'etsy/basic_client'
11
+ require 'etsy/secure_client'
12
+ require 'etsy/verification_request'
13
+
9
14
  require 'etsy/model'
10
15
  require 'etsy/user'
11
16
  require 'etsy/shop'
@@ -15,51 +20,124 @@ require 'etsy/image'
15
20
  # = Etsy: A friendly Ruby interface to the Etsy API
16
21
  #
17
22
  # == Quick Start
18
- #
19
- # Getting started is easy. First, you will need a valid API key from the Etsy
20
- # developer site (http://developer.etsy.com/). Since the API is read-only at
21
- # the moment, that's all you need to do.
22
- #
23
+ #
24
+ # Getting started is easy. First, you will need a valid API key from the Etsy
25
+ # developer site (http://developer.etsy.com/).
26
+ #
23
27
  # To start using the API, require the etsy gem and set it up to use your API key:
24
- #
28
+ #
25
29
  # require 'rubygems'
26
30
  # require 'etsy'
27
- #
31
+ #
28
32
  # Etsy.api_key = 'itsasecret'
29
- #
33
+ #
30
34
  # Now you can make API calls that originate from an Etsy user:
31
- #
35
+ #
32
36
  # # Find a user by username
33
37
  # user = Etsy.user('littletjane')
34
- #
38
+ #
35
39
  # # Grab that user's shop information
36
- # user.seller?
37
40
  # user.shop
38
41
  # user.shop.title
39
- #
42
+ #
40
43
  # # ... and the listings in the shop
41
44
  # listing = user.shop.listings.first
42
45
  # listing.title
43
46
  # listing.description
44
- #
47
+ #
45
48
  # To see what else is available for a user, check out the full documentation for
46
- # the Etsy::User class.
49
+ # the Etsy::User class. Information about making authenticated calls is available
50
+ # in the README.
47
51
  #
48
52
  module Etsy
49
-
50
- # Set the API key for all requests
51
- def self.api_key=(api_key)
52
- @api_key = api_key
53
+ class Error < RuntimeError; end
54
+
55
+ class << self
56
+ attr_accessor :api_key, :api_secret
57
+ attr_writer :callback_url
58
+ end
59
+
60
+ # Set the environment, accepts either :sandbox or :production. Defaults to :sandbox
61
+ # and will raise an exception when set to an unrecognized environment.
62
+ #
63
+ def self.environment=(environment)
64
+ unless [:sandbox, :production].include?(environment)
65
+ raise(ArgumentError, "environment must be set to either :sandbox or :production")
66
+ end
67
+ @environment = environment
68
+ end
69
+
70
+ # The currently configured environment.
71
+ #
72
+ def self.environment
73
+ @environment || :sandbox
74
+ end
75
+
76
+ # Set the access mode, can either be :read_only or :read_write. Defaults to :read_only
77
+ # and will raise an exception when set to an invalid value.
78
+ #
79
+ def self.access_mode=(mode)
80
+ unless [:read_only, :read_write].include?(mode)
81
+ raise(ArgumentError, "access mode must be set to either :read_only or :read_write")
82
+ end
83
+ @access_mode = mode
84
+ end
85
+
86
+ # The currently configured access mode
87
+ #
88
+ def self.access_mode
89
+ @access_mode || :read_only
53
90
  end
54
-
55
- # Retrieve the API key
56
- def self.api_key
57
- @api_key
91
+
92
+ # The configured callback URL or 'oob' if no callback URL is configured. This controls
93
+ # whether or not we need to pass the OAuth verifier by hand.
94
+ #
95
+ def self.callback_url
96
+ @callback_url || 'oob'
58
97
  end
59
-
98
+
60
99
  # Find a user by username. See Etsy::User for more information.
100
+ #
61
101
  def self.user(username)
62
- User.find_by_username(username)
102
+ User.find(username)
103
+ end
104
+
105
+ # Convenience method for accessing the authenticated user's own user information. Requires
106
+ # authentication.
107
+ #
108
+ def self.myself(token, secret)
109
+ User.myself(token, secret)
110
+ end
111
+
112
+ # Generate a request token for authorization.
113
+ #
114
+ def self.request_token
115
+ verification_request.request_token
116
+ end
117
+
118
+ # Generate an access token from the request token, secret, and verifier. The verifier can
119
+ # either be passed manually or from the params in the callback URL.
120
+ #
121
+ def self.access_token(request_token, request_secret, verifier)
122
+ @access_token ||= begin
123
+ client = Etsy::SecureClient.new({
124
+ :request_token => request_token,
125
+ :request_secret => request_secret,
126
+ :verifier => verifier
127
+ })
128
+ client.client
129
+ end
130
+ end
131
+
132
+ # Generate the URL to begin the verification process for a user.
133
+ #
134
+ def self.verification_url
135
+ verification_request.url
136
+ end
137
+
138
+ private
139
+
140
+ def self.verification_request
141
+ @verification_request ||= VerificationRequest.new
63
142
  end
64
-
65
- end
143
+ end
@@ -0,0 +1,26 @@
1
+ module Etsy
2
+
3
+ # = BasicClient
4
+ #
5
+ # Used for calling public API methods.
6
+ #
7
+ class BasicClient
8
+
9
+ # Create a new client that will connect to the specified host
10
+ #
11
+ def initialize(host)
12
+ @host = host
13
+ end
14
+
15
+ def client # :nodoc:
16
+ @client ||= Net::HTTP.new(@host)
17
+ end
18
+
19
+ # Fetch a raw response from the specified endpoint
20
+ #
21
+ def get(endpoint)
22
+ client.get(endpoint)
23
+ end
24
+
25
+ end
26
+ end
data/lib/etsy/image.rb CHANGED
@@ -1,27 +1,30 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = Image
4
4
  #
5
- # Represents an image resource of an Etsy listing and contains multiple sizes.
5
+ # Represents an image resource of an Etsy listing and contains multiple sizes.
6
6
  # Sizes available are:
7
7
  #
8
- # [small_square] The smallest square image (25x25 pixels)
9
- # [medium_square] The medium square image (50x50 pixels)
10
- # [large_square] The largest square image (75x75 pixels)
11
- # [small] The small image for this listing (155x125 pixels)
12
- # [medium] The medium image for this listing (200x200 pixels)
13
- # [large] The largest image available for this listing (430x? pixels)
8
+ # [square] The square image thumbnail (75x75 pixels)
9
+ # [small] The small image thumbnail (170x135 pixels)
10
+ # [thumbnail] The thumbnail for the image, no more than 570px wide
11
+ # [full] The full image for this listing, no more than 1500px wide
14
12
  #
15
13
  class Image
16
-
14
+
17
15
  include Etsy::Model
18
-
19
- attribute :small_square, :from => :image_url_25x25
20
- attribute :medium_square, :from => :image_url_50x50
21
- attribute :large_square, :from => :image_url_75x75
22
- attribute :small, :from => :image_url_155x125
23
- attribute :medium, :from => :image_url_200x200
24
- attribute :large, :from => :image_url_430xN
16
+
17
+ attribute :square, :from => :url_75x75
18
+ attribute :small, :from => :url_170x135
19
+ attribute :thumbnail, :from => :url_570xN
20
+ attribute :full, :from => :url_fullxfull
21
+
22
+ # Fetch all images for a given listing.
23
+ #
24
+ def self.find_all_by_listing_id(listing_id)
25
+ response = Request.get("/listings/#{listing_id}/images")
26
+ [response.result].flatten.map {|data| new(data) }
27
+ end
25
28
 
26
29
  end
27
30
  end
data/lib/etsy/listing.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Etsy
2
-
2
+
3
3
  # = Listing
4
4
  #
5
5
  # Represents a single Etsy listing. Has the following attributes:
@@ -24,53 +24,56 @@ module Etsy
24
24
  # [alchemy?] Is this listing an Alchemy item? (i.e. requested by an Etsy user)
25
25
  #
26
26
  class Listing
27
-
27
+
28
28
  include Etsy::Model
29
-
29
+
30
30
  STATES = %w(active removed sold_out expired alchemy)
31
-
32
- finder :all, '/shops/:user_id/listings'
33
-
31
+
34
32
  attribute :id, :from => :listing_id
35
33
  attribute :view_count, :from => :views
36
- attribute :created, :from => :creation_epoch
34
+ attribute :created, :from => :creation_tsz
37
35
  attribute :currency, :from => :currency_code
38
- attribute :ending, :from => :ending_epoch
39
-
40
- attributes :title, :description, :state, :url, :price, :quantity,
36
+ attribute :ending, :from => :ending_tsz
37
+
38
+ attributes :title, :description, :state, :url, :price, :quantity,
41
39
  :tags, :materials
42
40
 
41
+ # Retrieve all active listings for a given shop. Pulls back the first 25 listings.
42
+ #
43
+ def self.find_all_by_shop_id(shop_id)
44
+ response = Request.get("/shops/#{shop_id}/listings/active")
45
+ [response.result].flatten.map {|data| new(data) }
46
+ end
47
+
48
+ # The collection of images associated with this listing.
49
+ #
50
+ def images
51
+ @images ||= Image.find_all_by_listing_id(id)
52
+ end
53
+
54
+ # The primary image for this listing.
55
+ #
56
+ def image
57
+ images.first
58
+ end
59
+
43
60
  STATES.each do |state|
44
61
  define_method "#{state}?" do
45
62
  self.state == state.sub('_', '')
46
63
  end
47
64
  end
48
-
65
+
49
66
  # Time that this listing was created
50
67
  #
51
68
  def created_at
52
69
  Time.at(created)
53
70
  end
54
-
71
+
55
72
  # Time that this listing is ending (will be removed from store)
56
73
  #
57
74
  def ending_at
58
75
  Time.at(ending)
59
76
  end
60
-
61
- # The list of images associated with this listing. See Etsy::Image
62
- # for more information
63
- #
64
- def images
65
- @result['all_images'].map {|image_data| Image.new(image_data) }
66
- end
67
-
68
- # The primary image for this listing. See Etsy::Image for more
69
- # information
70
- #
71
- def image
72
- images.first
73
- end
74
-
77
+
75
78
  end
76
79
  end