numerousapp 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/numerousapp.rb +195 -69
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bc753ba4ac5af22bec9d355a0450327a4c0415c
|
4
|
+
data.tar.gz: e5e47a6f1f2a339338bdb5fe60a66366f33fa610
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aba6414701c7c0ddf0e33379f3dc35a3b48070587e535be8e57ceea8247f564cc2a8a9429ac67c32dd0a028faf2ee313d548c41f18a1dbb3943ff17e9df775d1
|
7
|
+
data.tar.gz: bfbae46781b680556c3cec93a0f380d7a765443c54b9d672a6d0ca54bf4c71b4ca6bce24241a431e0b626a1f2522a31f49f0ef8f9aaf0136f853d65df149cdf1
|
data/lib/numerousapp.rb
CHANGED
@@ -9,10 +9,10 @@
|
|
9
9
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
10
|
# copies of the Software, and to permit persons to whom the Software is
|
11
11
|
# furnished to do so, subject to the following conditions:
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# The above copyright notice and this permission notice shall be included in
|
14
14
|
# all copies or substantial portions of the Software.
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
17
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
18
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
@@ -26,7 +26,7 @@
|
|
26
26
|
# Numerous
|
27
27
|
# -- server-level operations: get user parameters, create metric, etc
|
28
28
|
#
|
29
|
-
# NumerousMetric
|
29
|
+
# NumerousMetric
|
30
30
|
# -- individual metrics: read/write/like/comment, etc
|
31
31
|
#
|
32
32
|
# NumerousClientInternals
|
@@ -72,31 +72,29 @@ end
|
|
72
72
|
|
73
73
|
#
|
74
74
|
# A NumerousMetricConflictError occurs when you write to a metric
|
75
|
-
# and specified "only if changed" and your value
|
75
|
+
# and specified "only if changed" and your value
|
76
76
|
# was (already) the current value
|
77
|
-
#
|
77
|
+
#
|
78
78
|
class NumerousMetricConflictError < NumerousError
|
79
79
|
end
|
80
80
|
|
81
81
|
#
|
82
|
-
# == NumerousClientInternals
|
82
|
+
# == NumerousClientInternals
|
83
83
|
#
|
84
84
|
# Handles details of talking to the numerousapp.com server, including
|
85
85
|
# the (basic) authentication, handling chunked APIs, json vs multipart APIs,
|
86
|
-
# fixing up a few server response quirks, and so forth. It is not meant
|
86
|
+
# fixing up a few server response quirks, and so forth. It is not meant
|
87
87
|
# for use outside of Numerous and NumerousMetric.
|
88
88
|
#
|
89
89
|
class NumerousClientInternals
|
90
90
|
|
91
|
-
|
92
|
-
|
93
91
|
#
|
94
92
|
# @param apiKey [String] API authentication key
|
95
93
|
# @param server [String] Optional (keyword arg). Server name.
|
96
94
|
# @param throttle [Proc] Optional throttle policy
|
97
95
|
# @param throttleData [Any] Optional data for throttle
|
98
96
|
#
|
99
|
-
# @!attribute agentString
|
97
|
+
# @!attribute agentString
|
100
98
|
# @return [String] User agent string sent to the server.
|
101
99
|
#
|
102
100
|
# @!attribute [r] serverName
|
@@ -114,7 +112,7 @@ class NumerousClientInternals
|
|
114
112
|
|
115
113
|
@serverName = server
|
116
114
|
@auth = { user: apiKey, password: "" }
|
117
|
-
u = URI.parse("https://"+server)
|
115
|
+
u = URI.parse("https://"+server)
|
118
116
|
@http = Net::HTTP.new(server, u.port)
|
119
117
|
@http.use_ssl = true # always required by NumerousApp
|
120
118
|
|
@@ -135,7 +133,12 @@ class NumerousClientInternals
|
|
135
133
|
# and the default policy uses the "data" (40) as the voluntary backoff point
|
136
134
|
#
|
137
135
|
@arbitraryMaximumTries = 10
|
138
|
-
|
136
|
+
voluntary = { voluntary: 40}
|
137
|
+
# you can keep the dflt throttle but just alter the voluntary param, this way:
|
138
|
+
if throttleData and not throttle
|
139
|
+
voluntary = throttleData
|
140
|
+
end
|
141
|
+
@throttlePolicy = [ThrottleDefault, voluntary, nil]
|
139
142
|
if throttle
|
140
143
|
@throttlePolicy = [throttle, throttleData, @throttlePolicy]
|
141
144
|
end
|
@@ -148,9 +151,14 @@ class NumerousClientInternals
|
|
148
151
|
attr_reader :serverName, :debugLevel
|
149
152
|
attr_reader :statistics
|
150
153
|
|
154
|
+
def to_s()
|
155
|
+
oid = (2 * self.object_id).to_s(16)
|
156
|
+
return "<Numerous {#{@serverName}} @ 0x#{oid}>"
|
157
|
+
end
|
158
|
+
|
151
159
|
# Set the debug level
|
152
160
|
#
|
153
|
-
# @param [Fixnum] lvl
|
161
|
+
# @param [Fixnum] lvl
|
154
162
|
# The desired debugging level. Greater than zero turns on debugging.
|
155
163
|
# @return [Fixnum] the previous debugging level.
|
156
164
|
def debug(lvl=1)
|
@@ -174,7 +182,7 @@ class NumerousClientInternals
|
|
174
182
|
|
175
183
|
protected
|
176
184
|
|
177
|
-
VersionString = '
|
185
|
+
VersionString = '20150215-1.0.2'
|
178
186
|
|
179
187
|
MethMap = {
|
180
188
|
GET: Net::HTTP::Get,
|
@@ -251,7 +259,7 @@ class NumerousClientInternals
|
|
251
259
|
|
252
260
|
|
253
261
|
# ALL api exchanges with the Numerous server go through here except
|
254
|
-
# for getRedirect() which is a special case (hack) for photo URLs
|
262
|
+
# for getRedirect() which is a special case (hack) for photo URLs
|
255
263
|
#
|
256
264
|
# Any single request/response uses this; chunked APIs use
|
257
265
|
# the iterator classes (which in turn come back and use this repeatedly)
|
@@ -270,7 +278,7 @@ class NumerousClientInternals
|
|
270
278
|
# in a url and it will take precedence over the basePath if any is present
|
271
279
|
#
|
272
280
|
# You can pass in a dictionary jdict which will be json-ified
|
273
|
-
# and sent as Content-Type: application/json. Or you can pass in
|
281
|
+
# and sent as Content-Type: application/json. Or you can pass in
|
274
282
|
# a multipart dictionary ... this is used for posting photos
|
275
283
|
# You should not specify both jdict and multipart
|
276
284
|
#
|
@@ -289,7 +297,7 @@ class NumerousClientInternals
|
|
289
297
|
# need to add logic. XXX TODO XXX
|
290
298
|
path = URI.parse(url).request_uri
|
291
299
|
end
|
292
|
-
|
300
|
+
|
293
301
|
rq = MethMap[api[:httpMethod]].new(path)
|
294
302
|
rq.basic_auth(@auth[:user], @auth[:password])
|
295
303
|
rq['user-agent'] = @agentString
|
@@ -333,7 +341,19 @@ class NumerousClientInternals
|
|
333
341
|
@arbitraryMaximumTries.times do |attempt|
|
334
342
|
|
335
343
|
@statistics[:serverRequests] += 1
|
344
|
+
t0 = Time.now
|
336
345
|
resp = @http.request(rq)
|
346
|
+
et = Time.now - t0
|
347
|
+
# We report the elapsed round-trip time, either as a scalar (default)
|
348
|
+
# OR if you preset the :serverResponseTimes to be an array of length N
|
349
|
+
# then we keep the last N response times, thusly:
|
350
|
+
begin
|
351
|
+
times = @statistics[:serverResponseTimes]
|
352
|
+
times.insert(0, et)
|
353
|
+
times.pop()
|
354
|
+
rescue NoMethodError # just a scalar
|
355
|
+
@statistics[:serverResponseTimes] = et
|
356
|
+
end
|
337
357
|
|
338
358
|
if @debugLevel > 0
|
339
359
|
puts "Response headers:\n"
|
@@ -377,7 +397,7 @@ class NumerousClientInternals
|
|
377
397
|
# On some requests that return "nothing" the server
|
378
398
|
# returns {} ... on others it literally returns nothing.
|
379
399
|
if (not resp.body) or resp.body.length == 0
|
380
|
-
rj = {}
|
400
|
+
rj = {}
|
381
401
|
else
|
382
402
|
# this isn't supposed to happen... server bug?
|
383
403
|
raise e
|
@@ -460,13 +480,13 @@ class NumerousClientInternals
|
|
460
480
|
filterInfo[:prev] = filterInfo[:current]
|
461
481
|
filterInfo[:current] = {}
|
462
482
|
end
|
463
|
-
|
483
|
+
|
464
484
|
list = v[api[:list]]
|
465
485
|
nextURL = v[api[:next]]
|
466
486
|
|
467
487
|
# hand them out
|
468
488
|
if list # can be nil for a variety of reasons
|
469
|
-
list.each do |i|
|
489
|
+
list.each do |i|
|
470
490
|
|
471
491
|
# A note about duplicate filtering
|
472
492
|
#
|
@@ -563,6 +583,9 @@ class NumerousClientInternals
|
|
563
583
|
|
564
584
|
if attempt > 0
|
565
585
|
stats[:throttleMultipleAttempts] += 1
|
586
|
+
if attempt > stats[:throttleMaxAttempt]
|
587
|
+
stats[:throttleMaxAttempt] = attempt
|
588
|
+
end
|
566
589
|
end
|
567
590
|
|
568
591
|
backarray = [ 2, 5, 15, 30, 60 ]
|
@@ -586,12 +609,12 @@ class NumerousClientInternals
|
|
586
609
|
# note that some errors don't communicate rateleft so we have to
|
587
610
|
# check for that as well (will be -1 here if wasn't sent to us)
|
588
611
|
#
|
589
|
-
# at constructor time our "throttle data" (td) was set up
|
612
|
+
# at constructor time our "throttle data" (td) was set up with the
|
590
613
|
# voluntary arbitrary limit
|
591
|
-
if rateleft >= 0 and rateleft < td
|
614
|
+
if rateleft >= 0 and rateleft < td[:voluntary]
|
592
615
|
stats[:throttleVoluntaryBackoff] += 1
|
593
616
|
# arbitrary .. 1 second if more than half left, 3 seconds if less
|
594
|
-
if (rateleft*2) > td
|
617
|
+
if (rateleft*2) > td[:voluntary]
|
595
618
|
sleep(1)
|
596
619
|
else
|
597
620
|
sleep(3)
|
@@ -631,18 +654,18 @@ end
|
|
631
654
|
# Hash of <string-key, value> pairs and returned from the appropriate methods.
|
632
655
|
#
|
633
656
|
# For some operations the server returns only a success/failure code.
|
634
|
-
# In those cases there is no useful return value from the method; the
|
657
|
+
# In those cases there is no useful return value from the method; the
|
635
658
|
# method succeeds or else raises an exception (containing the failure code).
|
636
659
|
#
|
637
660
|
# For the collection operations the server returns a JSON array of dictionary
|
638
661
|
# representations, possibly "chunked" into multiple request/response operations.
|
639
|
-
# The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
|
662
|
+
# The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
|
640
663
|
# the details of the chunking from you. They simply yield each individual
|
641
664
|
# item (string-key Hash) to your block.
|
642
665
|
#
|
643
666
|
# === Exception handling
|
644
667
|
#
|
645
|
-
# Almost every API that interacts with the server will potentially
|
668
|
+
# Almost every API that interacts with the server will potentially
|
646
669
|
# raise a {NumerousError} (or subclass thereof). This is not noted specifically
|
647
670
|
# in the doc for each method unless it might be considered a "surprise"
|
648
671
|
# (e.g., ping always returns true else raises an exception). Rescue as needed.
|
@@ -651,15 +674,15 @@ end
|
|
651
674
|
class Numerous < NumerousClientInternals
|
652
675
|
|
653
676
|
# path info for the server-level APIs: create a metric, get user info, etc
|
654
|
-
APIInfo = {
|
677
|
+
APIInfo = {
|
655
678
|
# POST to this to create a metric
|
656
|
-
create: {
|
679
|
+
create: {
|
657
680
|
path: '/v1/metrics',
|
658
681
|
POST: { successCodes: [ 201 ] }
|
659
682
|
},
|
660
683
|
|
661
684
|
# GET a users metric collection
|
662
|
-
metricsCollection: {
|
685
|
+
metricsCollection: {
|
663
686
|
path: '/v2/users/%{userId}/metrics',
|
664
687
|
defaults: { userId: 'me' },
|
665
688
|
GET: { next: 'nextURL', list: 'metrics' }
|
@@ -721,7 +744,7 @@ class Numerous < NumerousClientInternals
|
|
721
744
|
#
|
722
745
|
# All metrics for the given user (default is your own)
|
723
746
|
#
|
724
|
-
# @param [String] userId
|
747
|
+
# @param [String] userId
|
725
748
|
# optional - numeric id (represented as a string) of user
|
726
749
|
# @yield [m] metrics
|
727
750
|
# @yieldparam m [Hash] String-key representation of one metric
|
@@ -735,7 +758,7 @@ class Numerous < NumerousClientInternals
|
|
735
758
|
#
|
736
759
|
# All subscriptions for the given user (default is your own)
|
737
760
|
#
|
738
|
-
# @param [String] userId
|
761
|
+
# @param [String] userId
|
739
762
|
# optional - numeric id (represented as a string) of user
|
740
763
|
# @yield [s] subscriptions
|
741
764
|
# @yieldparam s [Hash] String-key representation of one subscription
|
@@ -744,7 +767,7 @@ class Numerous < NumerousClientInternals
|
|
744
767
|
def subscriptions(userId:nil, &block)
|
745
768
|
chunkedIterator(APIInfo[:subscriptions], { userId: userId }, block)
|
746
769
|
return self
|
747
|
-
end
|
770
|
+
end
|
748
771
|
|
749
772
|
|
750
773
|
#
|
@@ -754,7 +777,7 @@ class Numerous < NumerousClientInternals
|
|
754
777
|
#
|
755
778
|
# @param [Fixnum] count
|
756
779
|
# optional - number of metrics to return
|
757
|
-
# @return [Array] Array of hashes (metric string dicts). Each element
|
780
|
+
# @return [Array] Array of hashes (metric string dicts). Each element
|
758
781
|
# represents a particular popular metric.
|
759
782
|
#
|
760
783
|
def mostPopular(count:nil)
|
@@ -765,7 +788,7 @@ class Numerous < NumerousClientInternals
|
|
765
788
|
#
|
766
789
|
# Verify connectivity to the server
|
767
790
|
#
|
768
|
-
# @return [true] Always returns true connectivity if ok.
|
791
|
+
# @return [true] Always returns true connectivity if ok.
|
769
792
|
# Raises an exception otherwise.
|
770
793
|
# @raise [NumerousAuthError] Your credentials are no good.
|
771
794
|
# @raise [NumerousError] Other server (or network) errors.
|
@@ -781,10 +804,10 @@ class Numerous < NumerousClientInternals
|
|
781
804
|
# @param label [String] Required. Label for the metric.
|
782
805
|
# @param value [Fixnum,Float] Optional (keyword arg). Initial value.
|
783
806
|
# @param attrs [Hash] Optional (keyword arg). Initial attributes.
|
784
|
-
# @return [NumerousMetric]
|
807
|
+
# @return [NumerousMetric]
|
785
808
|
#
|
786
809
|
# @example Create a metric with label 'bozo' and set value to 17
|
787
|
-
# nr = Numerous.new('nmrs_3vblahblah')
|
810
|
+
# nr = Numerous.new('nmrs_3vblahblah')
|
788
811
|
# m = nr.createMetric('bozo')
|
789
812
|
# m.write(17)
|
790
813
|
#
|
@@ -810,7 +833,7 @@ class Numerous < NumerousClientInternals
|
|
810
833
|
#
|
811
834
|
# Instantiate a metric object to access a metric from the server.
|
812
835
|
# @return [NumerousMetric] metric object
|
813
|
-
# @param id [String]
|
836
|
+
# @param id [String]
|
814
837
|
# Required. Metric ID (something like '2319923751024'). NOTE: If id
|
815
838
|
# is bogus this will still "work" but (of course) errors will be
|
816
839
|
# raised when you do something with the metric.
|
@@ -827,9 +850,8 @@ class Numerous < NumerousClientInternals
|
|
827
850
|
# instead of an ID, and can even process it as a regexp.
|
828
851
|
#
|
829
852
|
# @param [String] labelspec The name (label) or regexp
|
830
|
-
# @param [String] matchType 'FIRST'
|
831
|
-
#
|
832
|
-
# The {Numerous} object that will be used to access this metric.
|
853
|
+
# @param [String] matchType 'FIRST','BEST','ONE','STRING' or 'ID'
|
854
|
+
#
|
833
855
|
def metricByLabel(labelspec, matchType:'FIRST')
|
834
856
|
def raiseConflict(s1,s2)
|
835
857
|
raise NumerousMetricConflictError.new("Multiple matches", 409, [s1, s2])
|
@@ -840,10 +862,22 @@ class Numerous < NumerousClientInternals
|
|
840
862
|
if not matchType
|
841
863
|
matchType = 'FIRST'
|
842
864
|
end
|
843
|
-
if not ['FIRST', 'BEST', 'ONE', 'STRING'].include?(matchType)
|
865
|
+
if not ['FIRST', 'BEST', 'ONE', 'STRING', 'ID'].include?(matchType)
|
844
866
|
raise ArgumentError
|
845
867
|
end
|
846
868
|
|
869
|
+
# Having 'ID' as an option simplifies some automated use cases
|
870
|
+
# (e.g., test vectors) because you can just pair ids and matchTypes
|
871
|
+
# and simply always call ByLabel even for native (nonlabel) IDs
|
872
|
+
# We add the semantics that the result is validated as an actual ID
|
873
|
+
if matchType == 'ID'
|
874
|
+
rv = metric(labelspec)
|
875
|
+
if not rv.validate()
|
876
|
+
rv = nil
|
877
|
+
end
|
878
|
+
return rv
|
879
|
+
end
|
880
|
+
|
847
881
|
# if you specified STRING and sent a regexp... or vice versa
|
848
882
|
if matchType == 'STRING' and labelspec.instance_of?(Regexp)
|
849
883
|
labelspec = labelspec.source
|
@@ -962,7 +996,7 @@ class Numerous < NumerousClientInternals
|
|
962
996
|
# replace() bcs there might be a trailing newline on naked creds
|
963
997
|
# (usually happens with a file or stdin)
|
964
998
|
j[credsAPIKey] = s.sub("\n",'')
|
965
|
-
end
|
999
|
+
end
|
966
1000
|
|
967
1001
|
return j[credsAPIKey]
|
968
1002
|
end
|
@@ -985,12 +1019,12 @@ end
|
|
985
1019
|
# will return just the naked number unless you ask it for the entire dictionary)
|
986
1020
|
#
|
987
1021
|
# For some operations the server returns only a success/failure code.
|
988
|
-
# In those cases there is no useful return value from the method; the
|
1022
|
+
# In those cases there is no useful return value from the method; the
|
989
1023
|
# method succeeds or else raises an exception (containing the failure code).
|
990
1024
|
#
|
991
1025
|
# For the collection operations the server returns a JSON array of dictionary
|
992
1026
|
# representations, possibly "chunked" into multiple request/response operations.
|
993
|
-
# The enumerator methods (e.g., "events") implement lazy-fetch and hide
|
1027
|
+
# The enumerator methods (e.g., "events") implement lazy-fetch and hide
|
994
1028
|
# the details of the chunking from you. They simply yield each individual
|
995
1029
|
# item (string-key Hash) to your block.
|
996
1030
|
#
|
@@ -1004,7 +1038,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1004
1038
|
# Constructor for a NumerousMetric
|
1005
1039
|
#
|
1006
1040
|
# @param [String] id The metric ID string.
|
1007
|
-
# @param [Numerous] nr
|
1041
|
+
# @param [Numerous] nr
|
1008
1042
|
# The {Numerous} object that will be used to access this metric.
|
1009
1043
|
#
|
1010
1044
|
# "id" should normally be the naked metric id (as a string).
|
@@ -1027,29 +1061,54 @@ class NumerousMetric < NumerousClientInternals
|
|
1027
1061
|
# the presence of a '/' indicates a non-naked metric ID. This
|
1028
1062
|
# seems a reasonable assumption given that IDs have to go into URLs
|
1029
1063
|
#
|
1064
|
+
# "id" can be a hash representing a metric or a subscription.
|
1065
|
+
# We will take (in order) key 'metricId' or key 'id' as the id.
|
1066
|
+
# This is convenient when using the metrics() or subscriptions() iterators.
|
1067
|
+
#
|
1068
|
+
# "id" can be an integer representing a metric ID. Not recommended
|
1069
|
+
# though it's handy sometimes in cut/paste interactive testing/use.
|
1070
|
+
#
|
1030
1071
|
|
1031
1072
|
def initialize(id, nr)
|
1073
|
+
actualId = nil
|
1032
1074
|
begin
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1075
|
+
fields = id.split('/')
|
1076
|
+
if fields.length() == 1
|
1077
|
+
actualId = fields[0]
|
1078
|
+
elsif fields[-2] == "m"
|
1079
|
+
actualId = fields[-1].to_i(36)
|
1080
|
+
else
|
1081
|
+
actualId = fields[-1]
|
1040
1082
|
end
|
1041
1083
|
rescue NoMethodError
|
1042
|
-
id = id.to_s
|
1043
1084
|
end
|
1044
1085
|
|
1045
|
-
|
1046
|
-
|
1086
|
+
if not actualId
|
1087
|
+
# it's not a string, see if it's a hash
|
1088
|
+
actualId = id['metricId'] || id['id']
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
if not actualId
|
1092
|
+
# well, see if it looks like an int
|
1093
|
+
i = id.to_i # allow this to raise exception if id bogus type here
|
1094
|
+
if i == id
|
1095
|
+
actualId = i.to_s
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
if not actualId
|
1100
|
+
raise ArgumentError("invalid id")
|
1101
|
+
else
|
1102
|
+
@id = actualId.to_s # defensive in case bad fmt in hash
|
1103
|
+
@nr = nr
|
1104
|
+
@cachedHash = nil
|
1105
|
+
end
|
1047
1106
|
end
|
1048
1107
|
attr_reader :id
|
1049
1108
|
attr_reader :nr
|
1050
1109
|
|
1051
1110
|
|
1052
|
-
APIInfo = {
|
1111
|
+
APIInfo = {
|
1053
1112
|
# read/update/delete a metric
|
1054
1113
|
metric: {
|
1055
1114
|
path: '/v1/metrics/%{metricId}' ,
|
@@ -1151,6 +1210,67 @@ class NumerousMetric < NumerousClientInternals
|
|
1151
1210
|
end
|
1152
1211
|
private :getAPI
|
1153
1212
|
|
1213
|
+
# for things, such as [ ], that need a cached copy of the metric's values
|
1214
|
+
def ensureCache()
|
1215
|
+
begin
|
1216
|
+
if not @cachedHash
|
1217
|
+
ignored = read() # just reading brings cache into being
|
1218
|
+
end
|
1219
|
+
rescue NumerousError => x
|
1220
|
+
raise x # definitely pass these along
|
1221
|
+
rescue => x # anything else turn into a NumerousError
|
1222
|
+
# this is usually going to be all sorts of random low-level
|
1223
|
+
# network problems (if the network fails at the exact wrong time)
|
1224
|
+
details = { exception: x }
|
1225
|
+
raise NumerousError.new("Could not obtain metric state", 0, details)
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
private :ensureCache
|
1229
|
+
|
1230
|
+
# access cached copy of metric via [ ]
|
1231
|
+
def [](idx)
|
1232
|
+
ensureCache()
|
1233
|
+
return @cachedHash[idx]
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# enumerator metric.each { |k, v| ... }
|
1237
|
+
def each()
|
1238
|
+
ensureCache()
|
1239
|
+
@cachedHash.each { |k, v| yield(k, v) }
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# produce the keys of a metric as an array
|
1243
|
+
def keys()
|
1244
|
+
ensureCache()
|
1245
|
+
return @cachedHash.keys
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
# string representation of a metric
|
1249
|
+
def to_s()
|
1250
|
+
# there's nothing important/magic about the object id displayed; however
|
1251
|
+
# to make it match the native to_s we (believe it or not) need to multiply
|
1252
|
+
# the object_id return value by 2. This is obviously implementation specific
|
1253
|
+
# (and makes no difference to anyone; but this way it "looks right" to humans)
|
1254
|
+
objid = (2*self.object_id).to_s(16) # XXX wow lol
|
1255
|
+
rslt = "<NumerousMetric @ 0x#{objid}: "
|
1256
|
+
begin
|
1257
|
+
ensureCache()
|
1258
|
+
lbl = self['label']
|
1259
|
+
val = self['value']
|
1260
|
+
rslt += "'#{self['label']}' [#@id] = #{val}>"
|
1261
|
+
rescue NumerousError => x
|
1262
|
+
puts(x.code)
|
1263
|
+
if x.code == 400
|
1264
|
+
rslt += "**INVALID-ID** '#@id'>"
|
1265
|
+
elsif x.code == 404
|
1266
|
+
rslt += "**ID NOT FOUND** '#@id'>"
|
1267
|
+
else
|
1268
|
+
rslt += "**SERVER-ERROR** '#{x.message}'>"
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
return rslt
|
1272
|
+
end
|
1273
|
+
|
1154
1274
|
#
|
1155
1275
|
# Read the current value of a metric
|
1156
1276
|
# @param [Boolean] dictionary
|
@@ -1162,13 +1282,14 @@ class NumerousMetric < NumerousClientInternals
|
|
1162
1282
|
def read(dictionary: false)
|
1163
1283
|
api = getAPI(:metric, :GET)
|
1164
1284
|
v = @nr.simpleAPI(api)
|
1285
|
+
@cachedHash = v.clone
|
1165
1286
|
return (if dictionary then v else v['value'] end)
|
1166
1287
|
end
|
1167
1288
|
|
1168
1289
|
# "Validate" a metric object.
|
1169
1290
|
# There really is no way to do this in any way that carries much weight.
|
1170
1291
|
# However, if a user gives you a metricId and you'd like to know if
|
1171
|
-
# that actually IS a metricId, this might be useful.
|
1292
|
+
# that actually IS a metricId, this might be useful.
|
1172
1293
|
#
|
1173
1294
|
# @example
|
1174
1295
|
# someId = ... get a metric ID from someone ...
|
@@ -1180,7 +1301,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1180
1301
|
# Realize that even a valid metric can be deleted asynchronously
|
1181
1302
|
# and thus become invalid after being validated by this method.
|
1182
1303
|
#
|
1183
|
-
# Reads the metric, catches the specific exceptions that occur for
|
1304
|
+
# Reads the metric, catches the specific exceptions that occur for
|
1184
1305
|
# invalid metric IDs, and returns True/False. Other exceptions mean
|
1185
1306
|
# something else went awry (server down, bad authentication, etc).
|
1186
1307
|
# @return [Boolean] validity of the metric
|
@@ -1189,7 +1310,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1189
1310
|
ignored = read()
|
1190
1311
|
return true
|
1191
1312
|
rescue NumerousError => e
|
1192
|
-
# bad request (400) is a completely bogus metric ID whereas
|
1313
|
+
# bad request (400) is a completely bogus metric ID whereas
|
1193
1314
|
# not found (404) is a well-formed ID that simply does not exist
|
1194
1315
|
if e.code == 400 or e.code == 404
|
1195
1316
|
return false
|
@@ -1230,7 +1351,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1230
1351
|
return self
|
1231
1352
|
end
|
1232
1353
|
|
1233
|
-
# Enumerate the interactions (like/comment/error) of a metric.
|
1354
|
+
# Enumerate the interactions (like/comment/error) of a metric.
|
1234
1355
|
#
|
1235
1356
|
# @yield [i] interactions
|
1236
1357
|
# @yieldparam i [Hash] String-key representation of one interaction.
|
@@ -1276,7 +1397,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1276
1397
|
# Obtain your subscription parameters on a given metric
|
1277
1398
|
#
|
1278
1399
|
# Note that normal users cannot see other user's subscriptions.
|
1279
|
-
# Thus the "userId" parameter is somewhat pointless; you can only
|
1400
|
+
# Thus the "userId" parameter is somewhat pointless; you can only
|
1280
1401
|
# ever see your own.
|
1281
1402
|
# @param [String] userId
|
1282
1403
|
# @return [Hash] your subscription attributes
|
@@ -1285,7 +1406,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1285
1406
|
return @nr.simpleAPI(api)
|
1286
1407
|
end
|
1287
1408
|
|
1288
|
-
# Subscribe to a metric.
|
1409
|
+
# Subscribe to a metric.
|
1289
1410
|
#
|
1290
1411
|
# See the NumerousApp API docs for what should be
|
1291
1412
|
# in the dict. This function will fetch the current parameters
|
@@ -1309,7 +1430,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1309
1430
|
end
|
1310
1431
|
|
1311
1432
|
dict.each { |k, v| params[k] = v }
|
1312
|
-
|
1433
|
+
@cachedHash = nil # bcs the subscriptions count changes
|
1313
1434
|
api = getAPI(:subscription, :PUT, { userId: userId })
|
1314
1435
|
return @nr.simpleAPI(api, jdict:params)
|
1315
1436
|
end
|
@@ -1318,7 +1439,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1318
1439
|
#
|
1319
1440
|
# @param [Fixnum|Float] newval Required. Value to be written.
|
1320
1441
|
#
|
1321
|
-
# @param [Boolean] onlyIf
|
1442
|
+
# @param [Boolean] onlyIf
|
1322
1443
|
# Optional (keyword arg). Only creates an event at the server
|
1323
1444
|
# if the newval is different from the current value. Raises
|
1324
1445
|
# NumerousMetricConflictError if there is no change in value.
|
@@ -1345,6 +1466,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1345
1466
|
j['action'] = 'ADD'
|
1346
1467
|
end
|
1347
1468
|
|
1469
|
+
@cachedHash = nil # will need to refresh cache on next access
|
1348
1470
|
api = getAPI(:events, :POST)
|
1349
1471
|
begin
|
1350
1472
|
v = @nr.simpleAPI(api, jdict:j)
|
@@ -1373,7 +1495,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1373
1495
|
# with your updates before writing them back. If true your supplied
|
1374
1496
|
# dictionary will become the entirety of the metric's parameters, and
|
1375
1497
|
# any parameters you did not include in your dictionary will revert to
|
1376
|
-
# their default values.
|
1498
|
+
# their default values.
|
1377
1499
|
# @return [Hash] string-key Hash of the new metric parameters.
|
1378
1500
|
#
|
1379
1501
|
def update(dict, overwriteAll:false)
|
@@ -1381,7 +1503,8 @@ class NumerousMetric < NumerousClientInternals
|
|
1381
1503
|
dict.each { |k, v| newParams[k] = v }
|
1382
1504
|
|
1383
1505
|
api = getAPI(:metric, :PUT)
|
1384
|
-
|
1506
|
+
@cachedHash = @nr.simpleAPI(api, jdict:newParams)
|
1507
|
+
return @cachedHash.clone
|
1385
1508
|
end
|
1386
1509
|
|
1387
1510
|
# common code for writing interactions
|
@@ -1439,7 +1562,8 @@ class NumerousMetric < NumerousClientInternals
|
|
1439
1562
|
def photo(imageDataOrReadable, mimeType:'image/jpeg')
|
1440
1563
|
api = getAPI(:photo, :POST)
|
1441
1564
|
mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
|
1442
|
-
|
1565
|
+
@cachedHash = @nr.simpleAPI(api, multipart: mpart)
|
1566
|
+
return @cachedHash.clone()
|
1443
1567
|
end
|
1444
1568
|
|
1445
1569
|
# Delete the metric's photo
|
@@ -1447,6 +1571,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1447
1571
|
# but the error code will be 200/OK.
|
1448
1572
|
# @return [nil]
|
1449
1573
|
def photoDelete
|
1574
|
+
@cachedHash = nil # I suppose we could have just deleted the photoURL
|
1450
1575
|
api = getAPI(:photo, :DELETE)
|
1451
1576
|
v = @nr.simpleAPI(api)
|
1452
1577
|
return nil
|
@@ -1476,9 +1601,9 @@ class NumerousMetric < NumerousClientInternals
|
|
1476
1601
|
|
1477
1602
|
# Obtain the underlying photoURL for a metric.
|
1478
1603
|
#
|
1479
|
-
# The photoURL is available in the metrics parameters so you could
|
1604
|
+
# The photoURL is available in the metrics parameters so you could
|
1480
1605
|
# just read(dictionary:true) and obtain it that way. However this goes
|
1481
|
-
# one step further ... the URL in the metric itself still requires
|
1606
|
+
# one step further ... the URL in the metric itself still requires
|
1482
1607
|
# authentication to fetch (it then redirects to the "real" underlying
|
1483
1608
|
# static photo URL). This function goes one level deeper and
|
1484
1609
|
# returns you an actual, publicly-fetchable, photo URL.
|
@@ -1525,6 +1650,7 @@ class NumerousMetric < NumerousClientInternals
|
|
1525
1650
|
#
|
1526
1651
|
# @return [nil]
|
1527
1652
|
def crushKillDestroy
|
1653
|
+
@cachedHash = nil
|
1528
1654
|
api = getAPI(:metric, :DELETE)
|
1529
1655
|
v = @nr.simpleAPI(api)
|
1530
1656
|
return nil
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numerousapp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Webber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Classes implementing the NumerousApp REST APIs for metrics. Requires
|
14
14
|
Ruby 2.x
|