Repository: pandas-dev/pandas

Issue:
Remote data access:  World Bank
A minor issue:   The function in pandas.io.data uses 2-letter codes to identify countries in data requests, rather than the more common 3-letter codes.  The latter is closer to the social norm at the iMF, World Bank, etc.  More important, they're easier to track down.  If you download the World Bank data (all of it), the data includes country names and 3-letter codes, but not 2-letter codes.  So to me the 3-letter codes would be more user-friendly.  

Cheers. 

http://pandas.pydata.org/pandas-docs/stable/remote_data.html#world-bank



Discussion:
this prob could be added pretty easily (and accept both 2/3 letter codes). interested in doing a pull-request?

Interesting idea, I'll give it a try.  I'm a newbie, though, making a
transition from Matlab and R.

On Mon, Oct 6, 2014 at 8:51 AM, jreback notifications@github.com wrote:

> this prob could be added pretty easily (and accept both 2/3 letter codes).
> interested in doing a pull-request?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pydata/pandas/issues/8482#issuecomment-58012057.

np. always welcome new contributors, see here for help: https://github.com/pydata/pandas/wiki

Cool, thank you.  I may have some candidates for the Cookbook in a month or
two.

More at https://github.com/DaveBackus/Data_Bootcamp

On Mon, Oct 6, 2014 at 10:58 AM, jreback notifications@github.com wrote:

> np. always welcome new contributors, see here for help:
> https://github.com/pydata/pandas/wiki
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pydata/pandas/issues/8482#issuecomment-58029600.

Dave, if you don't do a PR for this, I could probably tackle it.  Let me know if you want help.

Hi Jeff,

It'll be a while till I get to this, feel free to charge ahead.

Dave

On Fri, Oct 10, 2014 at 8:05 PM, Jeffrey McLarty notifications@github.com
wrote:

> Dave, if you don't do a PR for this, I could probably tackle it. Let me
> know if you want help.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pydata/pandas/issues/8482#issuecomment-58730556.


Resolving patch:
diff --git a/pandas/io/wb.py b/pandas/io/wb.py
--- a/pandas/io/wb.py
+++ b/pandas/io/wb.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 from __future__ import print_function
 
 from pandas.compat import map, reduce, range, lrange
@@ -5,10 +7,74 @@
 from pandas.io import json
 import pandas
 import numpy as np
+import warnings
+
+# This list of country codes was pulled from wikipedia during October 2014.
+# While some exceptions do exist, it is the best proxy for countries supported
+# by World Bank.  It is an aggregation of the 2-digit ISO 3166-1 alpha-2, and 
+# 3-digit ISO 3166-1 alpha-3, codes, with 'all', 'ALL', and 'All' appended ot 
+# the end.
 
+country_codes = ['AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', \
+                 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', \
+                 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', \
+                 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', \
+                 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', \
+                 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', \
+                 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', \
+                 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', \
+                 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', \
+                 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', \
+                 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', \
+                 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', \
+                 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', \
+                 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', \
+                 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', \
+                 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', \
+                 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', \
+                 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', \
+                 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', \
+                 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', \
+                 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', \
+                 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', \
+                 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', \
+                 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', \
+                 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW', \
+                 'ABW', 'AFG', 'AGO', 'AIA', 'ALA', 'ALB', 'AND', 'ARE', \
+                 'ARG', 'ARM', 'ASM', 'ATA', 'ATF', 'ATG', 'AUS', 'AUT', \
+                 'AZE', 'BDI', 'BEL', 'BEN', 'BES', 'BFA', 'BGD', 'BGR', \
+                 'BHR', 'BHS', 'BIH', 'BLM', 'BLR', 'BLZ', 'BMU', 'BOL', \
+                 'BRA', 'BRB', 'BRN', 'BTN', 'BVT', 'BWA', 'CAF', 'CAN', \
+                 'CCK', 'CHE', 'CHL', 'CHN', 'CIV', 'CMR', 'COD', 'COG', \
+                 'COK', 'COL', 'COM', 'CPV', 'CRI', 'CUB', 'CUW', 'CXR', \
+                 'CYM', 'CYP', 'CZE', 'DEU', 'DJI', 'DMA', 'DNK', 'DOM', \
+                 'DZA', 'ECU', 'EGY', 'ERI', 'ESH', 'ESP', 'EST', 'ETH', \
+                 'FIN', 'FJI', 'FLK', 'FRA', 'FRO', 'FSM', 'GAB', 'GBR', \
+                 'GEO', 'GGY', 'GHA', 'GIB', 'GIN', 'GLP', 'GMB', 'GNB', \
+                 'GNQ', 'GRC', 'GRD', 'GRL', 'GTM', 'GUF', 'GUM', 'GUY', \
+                 'HKG', 'HMD', 'HND', 'HRV', 'HTI', 'HUN', 'IDN', 'IMN', \
+                 'IND', 'IOT', 'IRL', 'IRN', 'IRQ', 'ISL', 'ISR', 'ITA', \
+                 'JAM', 'JEY', 'JOR', 'JPN', 'KAZ', 'KEN', 'KGZ', 'KHM', \
+                 'KIR', 'KNA', 'KOR', 'KWT', 'LAO', 'LBN', 'LBR', 'LBY', \
+                 'LCA', 'LIE', 'LKA', 'LSO', 'LTU', 'LUX', 'LVA', 'MAC', \
+                 'MAF', 'MAR', 'MCO', 'MDA', 'MDG', 'MDV', 'MEX', 'MHL', \
+                 'MKD', 'MLI', 'MLT', 'MMR', 'MNE', 'MNG', 'MNP', 'MOZ', \
+                 'MRT', 'MSR', 'MTQ', 'MUS', 'MWI', 'MYS', 'MYT', 'NAM', \
+                 'NCL', 'NER', 'NFK', 'NGA', 'NIC', 'NIU', 'NLD', 'NOR', \
+                 'NPL', 'NRU', 'NZL', 'OMN', 'PAK', 'PAN', 'PCN', 'PER', \
+                 'PHL', 'PLW', 'PNG', 'POL', 'PRI', 'PRK', 'PRT', 'PRY', \
+                 'PSE', 'PYF', 'QAT', 'REU', 'ROU', 'RUS', 'RWA', 'SAU', \
+                 'SDN', 'SEN', 'SGP', 'SGS', 'SHN', 'SJM', 'SLB', 'SLE', \
+                 'SLV', 'SMR', 'SOM', 'SPM', 'SRB', 'SSD', 'STP', 'SUR', \
+                 'SVK', 'SVN', 'SWE', 'SWZ', 'SXM', 'SYC', 'SYR', 'TCA', \
+                 'TCD', 'TGO', 'THA', 'TJK', 'TKL', 'TKM', 'TLS', 'TON', \
+                 'TTO', 'TUN', 'TUR', 'TUV', 'TWN', 'TZA', 'UGA', 'UKR', \
+                 'UMI', 'URY', 'USA', 'UZB', 'VAT', 'VCT', 'VEN', 'VGB', \
+                 'VIR', 'VNM', 'VUT', 'WLF', 'WSM', 'YEM', 'ZAF', 'ZMB', \
+                 'ZWE', 'all', 'ALL', 'All']
 
-def download(country=['MX', 'CA', 'US'], indicator=['GDPPCKD', 'GDPPCKN'],
-             start=2003, end=2005):
+def download(country=['MX', 'CA', 'US'], indicator=['NY.GDP.MKTP.CD', 'NY.GNS.ICTR.ZS'],
+             start=2003, end=2005,errors='warn'):
     """
     Download data series from the World Bank's World Development Indicators
 
@@ -17,91 +83,133 @@ def download(country=['MX', 'CA', 'US'], indicator=['GDPPCKD', 'GDPPCKN'],
 
     indicator: string or list of strings
         taken from the ``id`` field in ``WDIsearch()``
+        
     country: string or list of strings.
         ``all`` downloads data for all countries
-        ISO-2 character codes select individual countries (e.g.``US``,``CA``)
+        2 or 3 character ISO country codes select individual
+        countries (e.g.``US``,``CA``) or (e.g.``USA``,``CAN``).  The codes
+        can be mixed.
+            
+        The two ISO lists of countries, provided by wikipedia, are hardcoded
+        into pandas as of 11/10/2014.
+        
     start: int
         First year of the data series
+        
     end: int
         Last year of the data series (inclusive)
-
+    
+    errors: str {'ignore', 'warn', 'raise'}, default 'warn'
+        Country codes are validated against a hardcoded list.  This controls
+        the outcome of that validation, and attempts to also apply
+        to the results from world bank.
+        
+        errors='raise', will raise a ValueError on a bad country code.
+    
     Returns
     -------
 
-    ``pandas`` DataFrame with columns: country, iso2c, year, indicator value.
+    ``pandas`` DataFrame with columns: country, iso_code, year, 
+    indicator value.
+    
     """
 
-    # Are ISO-2 country codes valid?
-    valid_countries = [
-        "AG", "AL", "AM", "AO", "AR", "AT", "AU", "AZ", "BB", "BD", "BE", "BF",
-        "BG", "BH", "BI", "BJ", "BO", "BR", "BS", "BW", "BY", "BZ", "CA", "CD",
-        "CF", "CG", "CH", "CI", "CL", "CM", "CN", "CO", "CR", "CV", "CY", "CZ",
-        "DE", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "ER", "ES", "ET", "FI",
-        "FJ", "FR", "GA", "GB", "GE", "GH", "GM", "GN", "GQ", "GR", "GT", "GW",
-        "GY", "HK", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN", "IR", "IS",
-        "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KM", "KR", "KW", "KZ", "LA",
-        "LB", "LC", "LK", "LS", "LT", "LU", "LV", "MA", "MD", "MG", "MK", "ML",
-        "MN", "MR", "MU", "MW", "MX", "MY", "MZ", "NA", "NE", "NG", "NI", "NL",
-        "NO", "NP", "NZ", "OM", "PA", "PE", "PG", "PH", "PK", "PL", "PT", "PY",
-        "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SI", "SK", "SL",
-        "SN", "SR", "SV", "SY", "SZ", "TD", "TG", "TH", "TN", "TR", "TT", "TW",
-        "TZ", "UA", "UG", "US", "UY", "UZ", "VC", "VE", "VN", "VU", "YE", "ZA",
-        "ZM", "ZW", "all"
-    ]
     if type(country) == str:
         country = [country]
-    bad_countries = np.setdiff1d(country, valid_countries)
-    country = np.intersect1d(country, valid_countries)
-    country = ';'.join(country)
+
+    bad_countries = np.setdiff1d(country, country_codes)
+
+    # Validate the input
+    if len(bad_countries) > 0:
+        tmp = ", ".join(bad_countries)
+        if errors == 'raise':
+            raise ValueError("Invalid Country Code(s): %s" % tmp)
+        if errors == 'warn':
+            warnings.warn('Non-standard ISO country codes: %s' % tmp)
+
     # Work with a list of indicators
     if type(indicator) == str:
         indicator = [indicator]
+        
     # Download
     data = []
-    bad_indicators = []
+    bad_indicators = {}
     for ind in indicator:
-        try:
-            tmp = _get_data(ind, country, start, end)
-            tmp.columns = ['country', 'iso2c', 'year', ind]
-            data.append(tmp)
-        except:
-            bad_indicators.append(ind)
-    # Warn
-    if len(bad_indicators) > 0:
-        print('Failed to obtain indicator(s): %s' % '; '.join(bad_indicators))
-        print('The data may still be available for download at '
-              'http://data.worldbank.org')
-    if len(bad_countries) > 0:
-        print('Invalid ISO-2 codes: %s' % ' '.join(bad_countries))
-    # Merge WDI series
+        one_indicator_data,msg = _get_data(ind, country, start, end)
+        if msg == "Success":
+            data.append(one_indicator_data)
+        else:
+            bad_indicators[ind] = msg
+
+    if len(bad_indicators.keys()) > 0:
+        bad_ind_msgs = [i + " : " + m for i,m in bad_indicators.items()]
+        bad_ind_msgs = "\n\n".join(bad_ind_msgs)
+        bad_ind_msgs = "\n\nInvalid Indicators:\n\n%s" % bad_ind_msgs
+        if errors == 'raise':
+            raise ValueError(bad_ind_msgs)
+        if errors == 'warn':
+            warnings.warn(bad_ind_msgs)
+
+    # Confirm we actually got some data, and build Dataframe
     if len(data) > 0:
         out = reduce(lambda x, y: x.merge(y, how='outer'), data)
-        # Clean
-        out = out.drop('iso2c', axis=1)
+        out = out.drop('iso_code', axis=1)
         out = out.set_index(['country', 'year'])
         out = out.convert_objects(convert_numeric=True)
         return out
-
+    else:
+        msg = "No indicators returned data."
+        if errors == 'ignore':
+            msg += "  Set errors='warn' for more information."
+        raise ValueError(msg)
 
 def _get_data(indicator="NY.GNS.ICTR.GN.ZS", country='US',
               start=2002, end=2005):
+    
+    if type(country) == str:
+        country = [country]
+    
+    countries = ';'.join(country)
+        
     # Build URL for api call
-    url = ("http://api.worldbank.org/countries/" + country + "/indicators/" +
+    url = ("http://api.worldbank.org/countries/" + countries + "/indicators/" +
            indicator + "?date=" + str(start) + ":" + str(end) +
            "&per_page=25000&format=json")
+
     # Download
     with urlopen(url) as response:
         data = response.read()
+
+    # Check to see if there is a possible problem
+    possible_message = json.loads(data)[0]
+    if 'message' in possible_message.keys():
+        msg = possible_message['message'][0]
+        try:
+            msg = msg['key'].split() + ["\n "] + msg['value'].split()
+            wb_err = ' '.join(msg)
+        except:
+            wb_err = ""
+            if 'key' in msg.keys():
+                wb_err = msg['key'] + "\n "
+            if 'value' in msg.keys():
+                wb_err += msg['value']
+        error_msg = "Problem with a World Bank Query \n %s"
+        return None, error_msg % wb_err
+    
+    if 'total' in possible_message.keys():
+        if possible_message['total'] == 0:
+            return None, "No results from world bank."
+            
     # Parse JSON file
     data = json.loads(data)[1]
     country = [x['country']['value'] for x in data]
-    iso2c = [x['country']['id'] for x in data]
+    iso_code = [x['country']['id'] for x in data]
     year = [x['date'] for x in data]
     value = [x['value'] for x in data]
     # Prepare output
-    out = pandas.DataFrame([country, iso2c, year, value]).T
-    return out
-
+    out = pandas.DataFrame([country, iso_code, year, value]).T
+    out.columns = ['country', 'iso_code', 'year', indicator]
+    return out,"Success"
 
 def get_countries():
     '''Query information about countries
@@ -118,7 +226,6 @@ def get_countries():
     data = data.rename(columns={'id': 'iso3c', 'iso2Code': 'iso2c'})
     return data
 
-
 def get_indicators():
     '''Download information about all World Bank data series
     '''
@@ -146,7 +253,6 @@ def get_value(x):
     data.index = pandas.Index(lrange(data.shape[0]))
     return data
 
-
 _cached_series = None
 
 
@@ -190,3 +296,4 @@ def search(string='gdp.*capi', field='name', case=False):
     idx = data.str.contains(string, case=case)
     out = _cached_series.ix[idx].dropna()
     return out
+


Test patch:
diff --git a/pandas/io/tests/test_wb.py b/pandas/io/tests/test_wb.py
--- a/pandas/io/tests/test_wb.py
+++ b/pandas/io/tests/test_wb.py
@@ -14,19 +14,15 @@ class TestWB(tm.TestCase):
     @slow
     @network
     def test_wdi_search(self):
-        raise nose.SkipTest
-
-        expected = {u('id'): {2634: u('GDPPCKD'),
-                              4649: u('NY.GDP.PCAP.KD'),
-                              4651: u('NY.GDP.PCAP.KN'),
-                              4653: u('NY.GDP.PCAP.PP.KD')},
-                    u('name'): {2634: u('GDP per Capita, constant US$, '
-                                        'millions'),
-                                4649: u('GDP per capita (constant 2000 US$)'),
-                                4651: u('GDP per capita (constant LCU)'),
-                                4653: u('GDP per capita, PPP (constant 2005 '
+
+        expected = {u('id'): {6716: u('NY.GDP.PCAP.KD'),
+                              6718: u('NY.GDP.PCAP.KN'),
+                              6720: u('NY.GDP.PCAP.PP.KD')},
+                    u('name'): {6716: u('GDP per capita (constant 2005 US$)'),
+                                6718: u('GDP per capita (constant LCU)'),
+                                6720: u('GDP per capita, PPP (constant 2011 '
                                         'international $)')}}
-        result = search('gdp.*capita.*constant').ix[:, :2]
+        result = search('gdp.*capita.*constant').loc[6716:,['id','name']]
         expected = pandas.DataFrame(expected)
         expected.index = result.index
         assert_frame_equal(result, expected)
@@ -34,22 +30,93 @@ def test_wdi_search(self):
     @slow
     @network
     def test_wdi_download(self):
-        raise nose.SkipTest
 
-        expected = {'GDPPCKN': {(u('United States'), u('2003')): u('40800.0735367688'), (u('Canada'), u('2004')): u('37857.1261134552'), (u('United States'), u('2005')): u('42714.8594790102'), (u('Canada'), u('2003')): u('37081.4575704003'), (u('United States'), u('2004')): u('41826.1728310667'), (u('Mexico'), u('2003')): u('72720.0691255285'), (u('Mexico'), u('2004')): u('74751.6003347038'), (u('Mexico'), u('2005')): u('76200.2154469437'), (u('Canada'), u('2005')): u('38617.4563629611')}, 'GDPPCKD': {(u('United States'), u('2003')): u('40800.0735367688'), (u('Canada'), u('2004')): u('34397.055116118'), (u('United States'), u('2005')): u('42714.8594790102'), (u('Canada'), u('2003')): u('33692.2812368928'), (u('United States'), u('2004')): u('41826.1728310667'), (u('Mexico'), u('2003')): u('7608.43848670658'), (u('Mexico'), u('2004')): u('7820.99026814334'), (u('Mexico'), u('2005')): u('7972.55364129367'), (u('Canada'), u('2005')): u('35087.8925933298')}}
+        # Test a bad indicator with double (US), triple (USA),
+        # standard (CA, MX), non standard (KSV), 
+        # duplicated (US, US, USA), and unknown (BLA) country codes
+
+        # ...but NOT a crash inducing country code (World bank strips pandas 
+        #    users of the luxury of laziness, because they create their
+        #    own exceptions, and don't clean up legacy country codes.
+        # ...but NOT a retired indicator (User should want it to error.)
+
+        cntry_codes = ['CA', 'MX', 'USA', 'US', 'US', 'KSV', 'BLA']
+        inds = ['NY.GDP.PCAP.CD','BAD.INDICATOR']
+
+        expected = {'NY.GDP.PCAP.CD': {('Canada', '2003'): 28026.006013044702, ('Mexico', '2003'): 6601.0420648056606, ('Canada', '2004'): 31829.522562759001, ('Kosovo', '2003'): 1969.56271307405, ('Mexico', '2004'): 7042.0247834044303, ('United States', '2004'): 41928.886136479705, ('United States', '2003'): 39682.472247320402, ('Kosovo', '2004'): 2135.3328465238301}}
         expected = pandas.DataFrame(expected)
-        result = download(country=['CA', 'MX', 'US', 'junk'], indicator=['GDPPCKD',
-                                                                         'GDPPCKN', 'junk'], start=2003, end=2005)
+        expected.sort(inplace=True)
+        result = download(country=cntry_codes, indicator=inds,
+                          start=2003, end=2004, errors='ignore')
+        result.sort(inplace=True)
         expected.index = result.index
         assert_frame_equal(result, pandas.DataFrame(expected))
 
+    @slow
+    @network
+    def test_wdi_download_w_retired_indicator(self):
+
+        cntry_codes = ['CA', 'MX', 'US']
+        # Despite showing up in the search feature, and being listed online,
+        # the api calls to GDPPCKD don't work in their own query builder, nor
+        # pandas module.  GDPPCKD used to be a common symbol.
+        # This test is written to ensure that error messages to pandas users
+        # continue to make sense, rather than a user getting some missing
+        # key error, cause their JSON message format changed.  If 
+        # World bank ever finishes the deprecation of this symbol,
+        # this nose test should still pass.
+        
+        inds = ['GDPPCKD'] 
+
+        try:
+            result = download(country=cntry_codes, indicator=inds, 
+                              start=2003, end=2004, errors='ignore')
+        # If for some reason result actually ever has data, it's cause WB
+        # fixed the issue with this ticker.  Find another bad one.
+        except ValueError as e:
+            error_raised = True
+            error_msg = e.args[0]
+        
+            self.assertTrue(error_raised)
+            self.assertTrue("No indicators returned data." in error_msg)
+        
+        # if it ever gets here, it means WB unretired the indicator.
+        # even if they dropped it completely, it would still get caught above
+        # or the WB API changed somehow in a really unexpected way.
+        if len(result) > 0:
+            raise nose.SkipTest
+        
+        
+        
+    @slow
+    @network
+    def test_wdi_download_w_crash_inducing_countrycode(self):
+
+        cntry_codes = ['CA', 'MX', 'US', 'XXX']
+        inds = ['NY.GDP.PCAP.CD']
+
+        try:
+            result = download(country=cntry_codes, indicator=inds, 
+                              start=2003, end=2004, errors='ignore')
+        except ValueError as e:
+            error_raised = True
+            error_msg = e.args[0]
+        
+            self.assertTrue(error_raised)
+            self.assertTrue("No indicators returned data." in error_msg)
+        
+        # if it ever gets here, it means the country code XXX got used by WB
+        # or the WB API changed somehow in a really unexpected way.
+        if len(result) > 0:
+            raise nose.SkipTest
+        
     @slow
     @network
     def test_wdi_get_countries(self):
         result = get_countries()
         self.assertTrue('Zimbabwe' in list(result['name']))
-
+        self.assertTrue(len(result) > 100)
 
 if __name__ == '__main__':
     nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
-                   exit=False)
+                   exit=False)
\ No newline at end of file