File Coverage

blib/lib/Labkey/Query.pm
Criterion Covered Total %
statement 33 239 13.8
branch 0 98 0.0
condition 0 73 0.0
subroutine 11 20 55.0
pod 0 5 0.0
total 44 435 10.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             =head1 NAME
4              
5             Labkey::Query
6              
7             =head1 SYNOPSIS
8              
9             use Labkey::Query;
10             my $results = Labkey::Query::selectRows(
11             -baseUrl => 'http://labkey.com:8080/labkey/',
12             -containerPath => 'myFolder/',
13             -schemaName => 'lists',
14             -queryName => 'mid_tags',
15             );
16            
17             =head1 ABSTRACT
18              
19             For interacting with data in LabKey Server
20              
21             =head1 DESCRIPTION
22              
23             This module is designed to simplify querying and manipulating data in LabKey Server. It should more or less replicate the javascript APIs of the same names.
24              
25             After the module is installed, if you need to login with a specific user you
26             will need to create a .netrc file in the home directory of the user
27             running the perl script. Documentation on .netrc can be found here:
28             https://www.labkey.org/wiki/home/Documentation/page.view?name=netrc
29              
30             In API versions 0.08 and later, you can specify the param '-loginAsGuest'
31             which will query the server without any credentials. The server must permit
32             guest to that folder for this to work though.
33              
34             =head1 SEE ALSO
35              
36             The LabKey client APIs are described in greater detail here:
37             https://www.labkey.org/wiki/home/Documentation/page.view?name=viewAPIs
38              
39             Support questions should be directed to the LabKey forum:
40             https://www.labkey.org/announcements/home/Server/Forum/list.view?
41              
42             =head1 AUTHOR
43              
44             Ben Bimber
45              
46              
47             =head1 COPYRIGHT
48            
49             Copyright (c) 2010 Ben Bimber
50              
51             Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
52              
53             =cut
54              
55             package Labkey::Query;
56              
57 1     1   22470 use strict;
  1         2  
  1         30  
58 1     1   934 use LWP::UserAgent;
  1         64879  
  1         88  
59 1     1   8 use HTTP::Request;
  1         6  
  1         19  
60 1     1   1077 use JSON;
  1         15448  
  1         7  
61 1     1   1292 use Data::Dumper;
  1         7235  
  1         73  
62 1     1   789 use FileHandle;
  1         14264  
  1         7  
63 1     1   426 use File::Spec;
  1         2  
  1         26  
64 1     1   1130 use File::HomeDir;
  1         6984  
  1         59  
65 1     1   7 use Carp;
  1         2  
  1         46  
66 1     1   5 use URI;
  1         2  
  1         31  
67              
68 1     1   4 use vars qw($VERSION);
  1         1  
  1         3029  
69              
70             our $VERSION = "1.03";
71              
72              
73              
74             =head1 selectRows()
75              
76             selectRows() can be used to query data from LabKey server
77              
78             The following are the minimum required params:
79            
80             my $results = Labkey::Query::selectRows(
81             -baseUrl => 'http://labkey.com:8080/labkey/',
82             -containerPath => 'myFolder/',
83             -schemaName => 'lists',
84             -queryName => 'mid_tags',
85             );
86              
87             The following are optional:
88              
89             -viewName => 'view1',
90             -filterArray => [
91             ['file_active', 'eq', 1],
92             ['species', 'neq', 'zebra']
93             ], #allows filters to be applied to the query similar to the labkey Javascript API.
94             -parameters => [
95             ['enddate', '2011/01/01'],
96             ['totalDays', 15]
97             ], #allows parameters to be applied to the query similar to the labkey Javascript API.
98             -maxRows => 10 #the max number of rows returned
99             -sort => 'ColumnA,ColumnB' #sort order used for this query
100             -offset => 100 #the offset used when running the query
101             -columns => 'ColumnA,ColumnB' #A comma-delimited list of column names to include in the results.
102             -containerFilterName => 'currentAndSubfolders'
103             -debug => 1, #will result in a more verbose output
104             -loginAsGuest => #will not attempt to lookup credentials in netrc
105             -netrcFile => optional. the location of a file to use in place of a .netrc file. see also the environment variable LABKEY_NETRC.
106             -requiredVersion => 9.1 #if 8.3 is selected, it will use Labkey's pre-9.1 format for returning the data. 9.1 is the default. See documentation of LABKEY.Query.ExtendedSelectRowsResults for more detail here:
107             https://www.labkey.org/download/clientapi_docs/javascript-api/symbols/LABKEY.Query.html
108             -useragent => an instance of LWP::UserAgent (if not provided, a new instance will be created)
109             -timeout => timeout in seconds (used when creating a new LWP::UserAgent)
110            
111             NOTE:
112              
113             - In version 1.0 and later of the perl API, the default result format is 9.1. This is different from the LabKey JS, which defaults to the earlier format for legacy purposes.
114             - The environment variable 'LABKEY_URL' can be used instead of supplying a '-baseUrl' param
115             - The environment variable 'LABKEY_NETRC' can be used to specify an alternate location of a netrc file, if not in the user's home directory.
116              
117             =cut
118              
119             sub selectRows {
120              
121 0     0 0   my %args = @_;
122            
123             #allow baseUrl as environment variable
124 0   0       $args{'-baseUrl'} = $args{'-baseUrl'} || $ENV{LABKEY_URL};
125            
126             #sanity checking
127 0           my @required = ( '-containerPath', '-queryName', '-schemaName', '-baseUrl' );
128 0           foreach (@required) {
129 0 0         if ( !$args{$_} ) { croak("ERROR: Missing required param: $_") }
  0            
130             }
131              
132 0           my $url = URI->new(
133             _normalizeSlash($args{'-baseUrl'})
134             . "query/"
135             . _normalizeSlash($args{'-containerPath'})
136             . "getQuery.api?"
137             );
138            
139             #if no machine supplied, extract domain from baseUrl
140 0 0         if (!$args{'-machine'}){
141 0           $args{'-machine'} = $url->host;
142             }
143              
144 0           my $lk_config;
145 0   0       my $netrc_file = $args{-netrcFile} || $ENV{LABKEY_NETRC};
146 0 0         if(!$args{'-loginAsGuest'}){
147 0           $lk_config = _readrc( $args{-machine}, $netrc_file );
148             }
149            
150 0   0       my %params = (
151             schemaName => $args{'-schemaName'},
152             "query.queryName" => $args{'-queryName'},
153             apiVersion => $args{'-requiredVersion'} || 9.1,
154             );
155              
156 0           foreach ( @{ $args{-filterArray} } ) {
  0            
157 0           $params{"query." . @{$_}[0] . "~" . @{$_}[1]} = @{$_}[2] ;
  0            
  0            
  0            
158             }
159              
160 0           foreach ( @{ $args{-parameters} } ) {
  0            
161 0           $params{"query.param." . @{$_}[0]} = @{$_}[1];
  0            
  0            
162             }
163            
164 0           foreach ('viewName', 'offset', 'sort', 'maxRows', 'columns', 'containerFilterName'){
165 0 0         if ( $args{'-'.$_} ) {
166 0           $params{"query.".$_} = $args{'-'.$_};
167             }
168             }
169            
170 0           $url->query_form(%params);
171            
172 0 0         print $url."\n" if $args{-debug};
173              
174             #Fetch the actual data from the query
175 0           my $request = HTTP::Request->new( "GET" => $url );
176 0 0         if($lk_config){
177 0           $request->authorization_basic( $$lk_config{'login'}, $$lk_config{'password'} );
178             }
179 0   0       my $ua = $args{'-useragent'} || _createUserAgent( %args );
180 0           my $response = $ua->request($request);
181              
182             # Simple error checking
183 0 0         if ( $response->is_error ) {
184 0           croak( $response->status_line );
185             }
186              
187 0   0       my $json_obj = JSON->new->utf8->decode( $response->content )
188             || croak("ERROR: Unable to decode JSON.\n$url\n");
189              
190 0           return $json_obj;
191              
192             }
193              
194              
195             =head1 insertRows()
196              
197             insertRows() can be used to insert records into a LabKey table
198              
199             The following are the minimum required params:
200              
201             my $insert = Labkey::Query::insertRows(
202             -baseUrl => 'http://labkey.com:8080/labkey/',
203             -containerPath => 'myFolder/',
204             -schemaName => 'lists',
205             -queryName => 'backup',
206             -rows => [{
207             "JobName" => 'jobName',
208             "Status" => $status,
209             "Log" => $log,
210             "Date" => $date
211             }],
212             );
213            
214             The following are optional:
215              
216             -debug => 1, #will result in a more verbose output
217             -loginAsGuest => #will not attempt to lookup credentials in netrc
218             -netrcFile => optional. the location of a file to use in place of a .netrc file. see also the environment variable LABKEY_NETRC.
219             -useragent => an instance of LWP::UserAgent (if not provided, a new instance will be created)
220             -timeout => timeout in seconds (used when creating a new LWP::UserAgent)
221              
222             NOTE:
223             - The environment variable 'LABKEY_URL' can be used instead of supplying a '-baseUrl' param
224             - The environment variable 'LABKEY_NETRC' can be used to specify an alternate location of a netrc file, if not in the user's home directory
225              
226             =cut
227              
228             sub insertRows {
229 0     0 0   my %args = @_;
230              
231             #allow baseUrl as an environment variable
232 0   0       $args{'-baseUrl'} = $args{'-baseUrl'} || $ENV{LABKEY_URL};
233              
234             #sanity checking
235 0           my @required = ( '-containerPath', '-queryName', '-schemaName', '-baseUrl', '-rows' );
236 0           foreach (@required) {
237 0 0         if ( !$args{$_} ) { croak("ERROR: Missing required param: $_") }
  0            
238             }
239              
240             #if no machine supplied, extract domain from baseUrl
241 0 0         if (!$args{'-machine'}){
242 0           my $url = URI->new($args{'-baseUrl'});
243 0           $args{'-machine'} = $url->host;
244             }
245            
246 0           my $lk_config;
247 0   0       my $netrc_file = $args{-netrcFile} || $ENV{LABKEY_NETRC};
248 0 0         if(!$args{'-loginAsGuest'}){
249 0           $lk_config = _readrc( $args{-machine}, $netrc_file );
250             }
251              
252 0           my $url =
253             _normalizeSlash($args{'-baseUrl'})
254             . "query/"
255             . _normalizeSlash($args{'-containerPath'})
256             . "insertRows.api";
257              
258 0 0         print $url."\n" if $args{-debug};
259              
260 0           my $data = {
261             "schemaName" => $args{'-schemaName'},
262             "queryName" => $args{'-queryName'},
263             "rows" => $args{'-rows'}
264             };
265              
266 0   0       my $ua = $args{'-useragent'} || _createUserAgent( %args );
267 0           my $response = _postData($ua, $url, $data, $lk_config);
268 0           return $response;
269              
270             }
271              
272              
273             =head1 updateRows()
274              
275             updateRows() can be used to update records in a LabKey table
276              
277             The following are the minimum required params:
278              
279             my $update = Labkey::Query::updateRows(
280             -baseUrl => 'http://labkey.com:8080/labkey/',
281             -containerPath => 'myFolder/',
282             -schemaName => 'lists',
283             -queryName => 'backup',
284             -rows => [{
285             "JobName" => 'jobName',
286             "Status" => $status,
287             "Log" => $log,
288             "Date" => $date
289             }],
290             );
291            
292             The following are optional:
293              
294             -debug => 1, #will result in a more verbose output
295             -loginAsGuest => #will not attempt to lookup credentials in netrc
296             -netrcFile => optional. the location of a file to use in place of a .netrc file. see also the environment variable LABKEY_NETRC.
297             -useragent => an instance of LWP::UserAgent (if not provided, a new instance will be created)
298             -timeout => timeout in seconds (used when creating a new LWP::UserAgent)
299              
300             NOTE:
301             - The environment variable 'LABKEY_URL' can be used instead of supplying a '-baseUrl' param
302             - The environment variable 'LABKEY_NETRC' can be used to specify an alternate location of a netrc file, if not in the user's home directory
303            
304             =cut
305              
306             sub updateRows {
307 0     0 0   my %args = @_;
308              
309             #allow baseUrl as environment variable
310 0   0       $args{'-baseUrl'} = $args{'-baseUrl'} || $ENV{LABKEY_URL};
311              
312             #sanity checking
313 0           my @required = ( '-containerPath', '-queryName', '-schemaName', '-baseUrl', '-rows' );
314 0           foreach (@required) {
315 0 0         if ( !$args{$_} ) { croak("ERROR: Missing required param: $_") }
  0            
316             }
317              
318             #if no machine supplied, extract domain from baseUrl
319 0 0         if (!$args{'-machine'}){
320 0           my $url = URI->new($args{'-baseUrl'});
321 0           $args{'-machine'} = $url->host;
322             }
323 0           my $lk_config;
324 0   0       my $netrc_file = $args{-netrcFile} || $ENV{LABKEY_NETRC};
325 0 0         if(!$args{'-loginAsGuest'}){
326 0           $lk_config = _readrc( $args{-machine}, $netrc_file );
327             }
328              
329 0           my $url =
330             _normalizeSlash($args{'-baseUrl'})
331             . "query/"
332             . _normalizeSlash($args{'-containerPath'})
333             . "updateRows.api";
334              
335 0 0         print $url."\n" if $args{-debug};
336              
337 0           my $data = {
338             "schemaName" => $args{'-schemaName'},
339             "queryName" => $args{'-queryName'},
340             "rows" => $args{'-rows'}
341             };
342              
343 0   0       my $ua = $args{'-useragent'} || _createUserAgent( %args );
344 0           my $response = _postData($ua, $url, $data, $lk_config);
345 0           return $response;
346              
347             }
348              
349              
350             =head1 deleteRows()
351              
352             deleteRows() can be used to delete records in a LabKey table
353              
354             The following are the minimum required params:
355              
356             my $update = Labkey::Query::deleteRows(
357             -baseUrl => 'http://labkey.com:8080/labkey/',
358             -containerPath => 'myFolder/',
359             -schemaName => 'lists',
360             -queryName => 'backup',
361             -rows => [{
362             "Key" => '12',
363             }],
364             );
365            
366             The following are optional:
367              
368             -debug => 1, #will result in a more verbose output
369             -loginAsGuest => #will not attempt to lookup credentials in netrc
370             -netrcFile => optional. the location of a file to use in place of a .netrc file. see also the environment variable LABKEY_NETRC.
371             -useragent => an instance of LWP::UserAgent (if not provided, a new instance will be created)
372             -timeout => timeout in seconds (used when creating a new LWP::UserAgent)
373              
374             NOTE:
375             - The environment variable 'LABKEY_URL' can be used instead of supplying a '-baseUrl' param
376             - The environment variable 'LABKEY_NETRC' can be used to specify an alternate location of a netrc file, if not in the user's home directory
377            
378             =cut
379              
380             sub deleteRows {
381 0     0 0   my %args = @_;
382              
383             #allow baseUrl as environment variable
384 0   0       $args{'-baseUrl'} = $args{'-baseUrl'} || $ENV{LABKEY_URL};
385              
386             #sanity checking
387 0           my @required = ( '-containerPath', '-queryName', '-schemaName', '-baseUrl', '-rows' );
388 0           foreach (@required) {
389 0 0         if ( !$args{$_} ) { croak("ERROR: Missing required param: $_") }
  0            
390             }
391              
392             #if no machine supplied, extract domain from baseUrl
393 0 0         if (!$args{'-machine'}){
394 0           my $url = URI->new($args{'-baseUrl'});
395 0           $args{'-machine'} = $url->host;
396             }
397 0           my $lk_config;
398 0   0       my $netrc_file = $args{-netrcFile} || $ENV{LABKEY_NETRC};
399 0 0         if(!$args{'-loginAsGuest'}){
400 0           $lk_config = _readrc( $args{-machine}, $netrc_file );
401             }
402              
403 0           my $url =
404             _normalizeSlash($args{'-baseUrl'})
405             . "query/"
406             . _normalizeSlash($args{'-containerPath'})
407             . "deleteRows.api";
408              
409 0 0         print $url."\n" if $args{-debug};
410              
411 0           my $data = {
412             "schemaName" => $args{'-schemaName'},
413             "queryName" => $args{'-queryName'},
414             "rows" => $args{'-rows'}
415             };
416              
417 0   0       my $ua = $args{'-useragent'} || _createUserAgent( %args );
418 0           my $response = _postData($ua, $url, $data, $lk_config);
419 0           return $response;
420              
421             }
422              
423              
424             =head1 executeSql()
425              
426             executeSql() can be used to execute arbitrary SQL
427              
428             The following are the minimum required params:
429              
430             my $result = Labkey::Query::executeSql(
431             -baseUrl => 'http://labkey.com:8080/labkey/',
432             -containerPath => 'myFolder/',
433             -schemaName => 'study',
434             -sql => 'select MyDataset.foo, MyDataset.bar from MyDataset',
435             );
436            
437             The following are optional:
438              
439             -maxRows => 10 #the max number of rows returned
440             -sort => 'ColumnA,ColumnB' #sort order used for this query
441             -offset => 100 #the offset used when running the query
442             -containerFilterName => 'currentAndSubfolders'
443             -debug => 1, #will result in a more verbose output
444             -loginAsGuest => #will not attempt to lookup credentials in netrc
445             -netrcFile => optional. the location of a file to use in place of a .netrc file. see also the environment variable LABKEY_NETRC.
446             -useragent => an instance of LWP::UserAgent (if not provided, a new instance will be created)
447             -timeout => timeout in seconds (used when creating a new LWP::UserAgent)
448              
449             NOTE:
450             - The environment variable 'LABKEY_URL' can be used instead of supplying a '-baseUrl' param
451             - The environment variable 'LABKEY_NETRC' can be used to specify an alternate location of a netrc file, if not in the user's home directory
452            
453             =cut
454              
455             sub executeSql {
456 0     0 0   my %args = @_;
457              
458             #allow baseUrl as environment variable
459 0   0       $args{'-baseUrl'} = $args{'-baseUrl'} || $ENV{LABKEY_URL};
460              
461             #sanity checking
462 0           my @required = ( '-containerPath', '-baseUrl', '-sql' );
463 0           foreach (@required) {
464 0 0         if ( !$args{$_} ) { croak("ERROR: Missing required param: $_") }
  0            
465             }
466              
467             #if no machine supplied, extract domain from baseUrl
468 0 0         if (!$args{'-machine'}){
469 0           my $url = URI->new($args{'-baseUrl'});
470 0           $args{'-machine'} = $url->host;
471             }
472 0           my $lk_config;
473 0   0       my $netrc_file = $args{-netrcFile} || $ENV{LABKEY_NETRC};
474 0 0         if(!$args{'-loginAsGuest'}){
475 0           $lk_config = _readrc( $args{-machine}, $netrc_file );
476             }
477              
478 0           my $url =
479             _normalizeSlash($args{'-baseUrl'})
480             . "query/"
481             . _normalizeSlash($args{'-containerPath'})
482             . "executeSql.api?";
483              
484 0 0         print $url."\n" if $args{-debug};
485            
486 0           my $data = {
487             "schemaName" => $args{'-schemaName'},
488             "sql" => $args{'-sql'},
489             };
490            
491 0           foreach ('offset', 'sort', 'maxRows', 'containerFilterName'){
492 0 0         if ( $args{'-'.$_} ) {
493 0           $$data{$_} = $args{'-'.$_};
494             }
495             }
496            
497 0 0         print Dumper($data) if $args{-debug};
498            
499 0   0       my $ua = $args{'-useragent'} || _createUserAgent( %args );
500 0           my $response = _postData($ua, $url, $data, $lk_config);
501 0           return $response;
502              
503             }
504              
505              
506              
507              
508             # NOTE: this code adapted from Net::Netrc module. It was changed so alternate locations could be supplied for a .netrc file
509             sub _readrc() {
510              
511 0   0 0     my $host = shift || 'default';
512 0           my $file = shift;
513              
514             #allow user to supply netrc location
515 0 0 0       if(!$file || !-e $file){
516 0           $file = File::Spec->catfile( File::HomeDir::home(), '.netrc' );
517             }
518 0 0         if ( !-e $file ) {
519 0           $file = File::Spec->catfile( File::HomeDir::home(), '_netrc' );
520             }
521              
522 0           my %netrc = ();
523 0           my ( $login, $pass, $acct ) = ( undef, undef, undef );
524 0           my $fh;
525 0           local $_;
526              
527 0           $netrc{default} = undef;
528              
529             # OS/2 and Win32 do not handle stat in a way compatable with this check :-(
530 0 0 0       unless ( $^O eq 'os2'
      0        
      0        
531             || $^O eq 'MSWin32'
532             || $^O eq 'MacOS'
533             || $^O =~ /^cygwin/ )
534             {
535 0           my @stat = stat($file);
536              
537 0 0         if (@stat) {
538 0 0         if ( $stat[2] & 077 ) {
539 0           carp "Bad permissions: $file";
540 0           return;
541             }
542 0 0         if ( $stat[4] != $< ) {
543 0           carp "Not owner: $file";
544 0           return;
545             }
546             }
547             }
548              
549 0 0         if ( $fh = FileHandle->new( $file, "r" ) ) {
550 0           my ( $mach, $macdef, $tok, @tok ) = ( 0, 0 );
551              
552 0           while (<$fh>) {
553 0 0         undef $macdef if /\A\n\Z/;
554              
555 0 0         if ($macdef) {
556 0           push( @$macdef, $_ );
557 0           next;
558             }
559              
560 0           s/^\s*//;
561 0           chomp;
562              
563 0   0       while ( length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*// )
564             {
565 0           ( my $tok = $+ ) =~ s/\\(.)/$1/g;
566 0           push( @tok, $tok );
567             }
568              
569             TOKEN:
570 0           while (@tok) {
571 0 0         if ( $tok[0] eq "default" ) {
572 0           shift(@tok);
573 0           $mach = bless {};
574 0           $netrc{default} = [$mach];
575              
576 0           next TOKEN;
577             }
578              
579             last TOKEN
580 0 0         unless @tok > 1;
581              
582 0           $tok = shift(@tok);
583              
584 0 0         if ( $tok eq "machine" ) {
    0          
    0          
585 0           my $host = shift @tok;
586 0           $mach = { machine => $host };
587              
588 0 0         $netrc{$host} = []
589             unless exists( $netrc{$host} );
590 0           push( @{ $netrc{$host} }, $mach );
  0            
591             }
592             elsif ( $tok =~ /^(login|password|account)$/ ) {
593 0 0         next TOKEN unless $mach;
594 0           my $value = shift @tok;
595              
596             # Following line added by rmerrell to remove '/' escape char in .netrc
597 0           $value =~ s/\/\\/\\/g;
598 0           $mach->{$1} = $value;
599             }
600             elsif ( $tok eq "macdef" ) {
601 0 0         next TOKEN unless $mach;
602 0           my $value = shift @tok;
603 0 0         $mach->{macdef} = {}
604             unless exists $mach->{macdef};
605 0           $macdef = $mach->{machdef}{$value} = [];
606             }
607             }
608             }
609 0           $fh->close();
610             }
611            
612 0           my $auth = $netrc{$host}[0];
613              
614             #if no machine is specified and there is only 1 machine in netrc, we use that one
615 0 0 0       if (!$auth && length((keys %netrc))==1){
616 0           $auth = $netrc{(keys %netrc)[0]}[0];
617             }
618              
619 0 0         warn("Unable to find entry for host: $host") unless $auth;
620 0 0         warn("Missing password for host: $host") unless $auth->{password};
621 0 0         warn("Missing login for host: $host") unless $auth->{login};
622              
623 0           return $auth;
624             }
625              
626              
627             sub _normalizeSlash(){
628 0     0     my $containerPath = shift;
629            
630 0           $containerPath =~ s/^\///;
631 0           $containerPath =~ s/\/$//;
632 0           $containerPath .= '/';
633 0           return $containerPath;
634             }
635              
636              
637             sub _postData(){
638 0     0     my ($ua, $url, $data, $lk_config) = @_;
639            
640 0           my $json_obj = JSON->new->utf8->encode($data);
641              
642 0           my $req = new HTTP::Request;
643 0           $req->method('POST');
644 0           $req->url($url);
645 0           $req->content_type('application/json');
646 0           $req->content($json_obj);
647 0           $req->authorization_basic( $$lk_config{'login'}, $$lk_config{'password'} );
648 0           my $response = $ua->request($req);
649              
650             # Simple error checking
651 0 0         if ( $response->is_error ) {
652 0           croak($response->status_line);
653             }
654              
655             #print Dumper($response);
656 0   0       $json_obj = JSON->new->utf8->decode( $response->content )
657             || croak("ERROR: Unable to decode JSON.\n$url\n");
658            
659 0           return $json_obj;
660             }
661              
662             sub _createUserAgent() {
663 0     0     my %args = @_;
664              
665 0           my $ua = new LWP::UserAgent;
666 0           $ua->agent("Perl API Client/1.0");
667 0 0         if ($args{'-timeout'}) {
668 0           print "setting timeout to " . $args{'-timeout'} . "\n";
669 0           $ua->timeout($args{'-timeout'});
670             }
671 0           return $ua;
672             }
673              
674              
675             1;
676